├── .flake8 ├── .github ├── scripts │ └── current_version.py └── workflows │ ├── release.yml │ └── verify.yml ├── .gitignore ├── LICENSE ├── README.md ├── didcomm ├── __init__.py ├── common │ ├── __init__.py │ ├── algorithms.py │ ├── resolvers.py │ ├── types.py │ └── utils.py ├── core │ ├── __init__.py │ ├── anoncrypt.py │ ├── authcrypt.py │ ├── converters.py │ ├── defaults.py │ ├── from_prior.py │ ├── keys │ │ ├── __init__.py │ │ ├── anoncrypt_keys_selector.py │ │ ├── authcrypt_keys_selector.py │ │ ├── forward_next_keys_selector.py │ │ └── sign_keys_selector.py │ ├── serialization.py │ ├── sign.py │ ├── types.py │ ├── utils.py │ ├── validation.py │ └── validators.py ├── did_doc │ ├── __init__.py │ ├── did_doc.py │ ├── did_resolver.py │ └── did_resolver_in_memory.py ├── errors.py ├── message.py ├── pack_encrypted.py ├── pack_plaintext.py ├── pack_signed.py ├── protocols │ ├── __init__.py │ └── routing │ │ ├── __init__.py │ │ └── forward.py ├── secrets │ ├── __init__.py │ ├── secrets_resolver.py │ ├── secrets_resolver_demo.py │ ├── secrets_resolver_editable.py │ ├── secrets_resolver_in_memory.py │ └── secrets_util.py └── unpack.py ├── docs ├── release.md └── testing.md ├── poetry.lock ├── pyproject.toml ├── renovate.json └── tests ├── __init__.py ├── conftest.py ├── demo ├── __init__.py ├── conftest.py ├── test_demo.py ├── test_demo_advanced_params.py ├── test_demo_attachments.py ├── test_demo_mediators.py └── test_demo_rotate_keys.py ├── performance ├── __init__.py └── test_performance.py ├── test_vectors ├── __init__.py ├── common.py ├── did_doc │ ├── __init__.py │ ├── did_doc_alice.py │ ├── did_doc_bob.py │ ├── did_doc_charlie.py │ ├── did_doc_mediator1.py │ ├── did_doc_mediator2.py │ └── mock_did_resolver.py ├── didcomm_messages │ ├── __init__.py │ ├── messages.py │ ├── spec │ │ ├── __init__.py │ │ ├── spec_test_vectors_anon_encrypted.py │ │ ├── spec_test_vectors_auth_encrypted.py │ │ ├── spec_test_vectors_plaintext.py │ │ └── spec_test_vectors_signed.py │ └── tests │ │ ├── __init__.py │ │ ├── common.py │ │ ├── test_vectors_anoncrypt_negative.py │ │ ├── test_vectors_authcrypt_negative.py │ │ ├── test_vectors_plaintext_negative.py │ │ ├── test_vectors_plaintext_positive.py │ │ └── test_vectors_signed_negative.py ├── secrets │ ├── __init__.py │ ├── mock_secrets_resolver.py │ ├── mock_secrets_resolver_alice.py │ ├── mock_secrets_resolver_bob.py │ ├── mock_secrets_resolver_charlie.py │ ├── mock_secrets_resolver_charlie_rotated_to_alice.py │ ├── mock_secrets_resolver_mediator1.py │ └── mock_secrets_resolver_mediator2.py ├── test_utils.py └── utils.py └── unit ├── __init__.py ├── common.py ├── conftest.py ├── didcomm ├── __init__.py ├── message.py └── protocols │ └── routing │ ├── __init__.py │ ├── conftest.py │ ├── helper.py │ ├── test_did_service_helpers.py │ ├── test_forward_message.py │ ├── test_is_forward.py │ ├── test_unpack_forward.py │ └── test_wrap_in_forward.py ├── keys ├── __init__.py ├── conftest.py ├── test_anoncrypt_keys_selector.py ├── test_authcrypt_keys_selector.py ├── test_extract_key.py ├── test_extract_sign_alg.py └── test_sign_keys_selector.py ├── pack_common ├── __init__.py ├── conftest.py └── test_message_negative.py ├── pack_encrypted ├── __init__.py ├── conftest.py ├── test_message_to_multiple_recipients.py ├── test_pack_anon_encrypted.py ├── test_pack_auth_encrypted.py ├── test_pack_encrypted_helpers.py ├── test_pack_encrypted_negative.py ├── test_pack_encrypted_with_from_prior.py └── test_pack_encrypted_with_from_prior_negative.py ├── pack_plaintext ├── __init__.py ├── common.py ├── conftest.py ├── test_pack_plaintext.py ├── test_pack_plaintext_with_from_prior.py └── test_pack_plaintext_with_from_prior_negative.py ├── pack_signed ├── __init__.py ├── conftest.py ├── test_pack_signed.py ├── test_pack_signed_negative.py ├── test_pack_signed_with_from_prior.py └── test_pack_signed_with_from_prior_negative.py ├── secrets ├── __init__.py ├── test_secrets_resolver_demo.py └── test_secrets_util.py ├── spec_test_vectors ├── __init__.py ├── conftest.py ├── test_pack_unpack_anon_crypt.py ├── test_pack_unpack_auth_crypt.py ├── test_pack_unpack_plaintext.py └── test_pack_unpack_signed.py ├── unpack ├── __init__.py ├── conftest.py ├── test_unpack_encrypted.py ├── test_unpack_encrypted_negative.py ├── test_unpack_forward.py ├── test_unpack_plaintext.py ├── test_unpack_plaintext_negative.py ├── test_unpack_plaintext_with_from_prior.py ├── test_unpack_plaintext_with_from_prior_negative.py ├── test_unpack_signed.py └── test_unpack_signed_negative.py └── utils ├── __init__.py └── test_utils.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | # set the same as 'black' uses 3 | max-line-length = 88 4 | # TODO mention the reasons why we ignore the following 5 | # E203 slice notation whitespace 6 | # E501 line length 7 | # W503 line break before binary operator 8 | # W504 line break after binary operator 9 | # F841 local variable name is assigned to but never used 10 | ignore = E203,E501,W503,W504,F841 11 | -------------------------------------------------------------------------------- /.github/scripts/current_version.py: -------------------------------------------------------------------------------- 1 | """Print current version of package as GHA output.""" 2 | 3 | import tomli 4 | 5 | with open("pyproject.toml", "rb") as f: 6 | project = tomli.load(f) 7 | 8 | print("::set-output name=current_version::" + project["tool"]["poetry"]["version"]) 9 | -------------------------------------------------------------------------------- /.github/workflows/verify.yml: -------------------------------------------------------------------------------- 1 | name: verify 2 | 3 | on: 4 | pull_request: 5 | # paths: 6 | # - '**.py' 7 | 8 | 9 | env: 10 | PKG_NAME: didcomm 11 | 12 | 13 | jobs: 14 | 15 | release-ready: 16 | runs-on: ubuntu-latest 17 | if: github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'stable' 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - uses: actions/setup-python@v4 22 | id: setup 23 | with: 24 | python-version: '3.x' 25 | 26 | - name: Install dependencies 27 | run: pip install tomli 28 | 29 | - name: Get current version 30 | id: current_version 31 | run: python ./.github/scripts/current_version.py 32 | 33 | - name: Check version format 34 | run: | 35 | # verify the version has "MAJOR.MINOR.PATCH" parts only 36 | echo "${{ steps.current_version.outputs.current_version }}" | grep -e '^[0-9]\+\.[0-9]\+\.[0-9]\+$' 37 | shell: bash 38 | 39 | # TODO improve (DRY): copy-paste from release.yml 40 | - name: Get release info 41 | id: release_info 42 | run: | 43 | release_info="$(curl -s https://api.github.com/repos/${{ github.repository }}/releases \ 44 | | jq '.[] | select(.name == "v${{ steps.current_version.outputs.current_version }}")')" 45 | echo "::set-output name=release_info::$release_info" 46 | echo "$release_info" 47 | shell: bash 48 | 49 | - name: fail unless release not found 50 | # TODO check if greater than latest tag / release (?) 51 | if: steps.release_info.outputs.release_info 52 | run: exit 1 53 | 54 | static-black: 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v3 58 | 59 | - name: Set up Python 3.7 60 | id: setup 61 | uses: actions/setup-python@v4 62 | with: 63 | python-version: '3.7' 64 | 65 | - name: Install black 66 | run: pipx install black 67 | 68 | - name: Black Format Check 69 | run: black --check . 70 | 71 | static-flake8: 72 | runs-on: ubuntu-latest 73 | steps: 74 | - uses: actions/checkout@v3 75 | 76 | - name: Set up Python 3.7 77 | id: setup 78 | uses: actions/setup-python@v4 79 | with: 80 | python-version: '3.7' 81 | 82 | - name: Install flake8 83 | run: pipx install flake8 84 | 85 | - name: Lint with flake8 86 | run: flake8 . 87 | 88 | unit: 89 | strategy: 90 | matrix: 91 | python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ] 92 | os: [ ubuntu-latest, windows-latest, macos-latest ] 93 | runs-on: ${{ matrix.os }} 94 | steps: 95 | - uses: actions/checkout@v3 96 | 97 | - name: Set up Python ${{ matrix.python-version }} 98 | id: setup 99 | uses: actions/setup-python@v4 100 | with: 101 | python-version: ${{ matrix.python-version }} 102 | 103 | - name: Install poetry 104 | run: pipx install poetry --python ${{ steps.setup.outputs.python-path }} 105 | 106 | - name: Install dependencies 107 | run: poetry install 108 | 109 | - name: Test with pytest 110 | run: poetry run pytest 111 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | .idea 132 | 133 | # vim 134 | *.swp 135 | -------------------------------------------------------------------------------- /didcomm/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.3.1" 2 | 3 | from didcomm.common.algorithms import AnonCryptAlg, AuthCryptAlg, SignAlg 4 | from didcomm.common.resolvers import ResolversConfig 5 | from didcomm.common.types import ( 6 | DIDCommMessageMediaTypes, 7 | DIDCommMessageProtocolTypes, 8 | DIDCommMessageTypes, 9 | DIDDocServiceTypes, 10 | VerificationMethodType, 11 | VerificationMaterial, 12 | VerificationMaterialFormat, 13 | ) 14 | from didcomm.did_doc.did_doc import DIDDoc, DIDCommService, VerificationMethod 15 | from didcomm.did_doc.did_resolver import DIDResolver 16 | from didcomm.did_doc.did_resolver_in_memory import DIDResolverInMemory 17 | from didcomm.message import ( 18 | Attachment, 19 | AttachmentDataBase64, 20 | AttachmentDataJson, 21 | AttachmentDataLinks, 22 | FromPrior, 23 | GenericMessage, 24 | Message, 25 | ) 26 | from didcomm.pack_encrypted import ( 27 | pack_encrypted, 28 | PackEncryptedConfig, 29 | PackEncryptedParameters, 30 | PackEncryptedResult, 31 | ) 32 | from didcomm.pack_plaintext import ( 33 | pack_plaintext, 34 | PackPlaintextParameters, 35 | PackPlaintextResult, 36 | ) 37 | from didcomm.pack_signed import pack_signed, PackSignedParameters, PackSignedResult 38 | from didcomm.protocols.routing.forward import ( 39 | is_forward, 40 | unpack_forward, 41 | wrap_in_forward, 42 | ForwardBody, 43 | ForwardMessage, 44 | ForwardPackResult, 45 | ForwardResult, 46 | ) 47 | from didcomm.unpack import unpack, Metadata, UnpackConfig, UnpackResult 48 | from didcomm.secrets.secrets_resolver import Secret, SecretsResolver 49 | from didcomm.secrets.secrets_resolver_in_memory import SecretsResolverInMemory 50 | 51 | __all__ = [ 52 | # didcomm.common.algorithms 53 | "AnonCryptAlg", 54 | "AuthCryptAlg", 55 | "SignAlg", 56 | # didcomm.common.resolvers 57 | "ResolversConfig", 58 | # didcomm.common.types 59 | "DIDCommMessageMediaTypes", 60 | "DIDCommMessageProtocolTypes", 61 | "DIDCommMessageTypes", 62 | "DIDDocServiceTypes", 63 | "VerificationMethodType", 64 | "VerificationMaterial", 65 | "VerificationMaterialFormat", 66 | # didcomm.did_doc.did_doc 67 | "DIDDoc", 68 | "DIDCommService", 69 | "VerificationMethod", 70 | # didcomm.did_doc.did_resolver 71 | "DIDResolver", 72 | # did_resolver_in_memory 73 | "DIDResolverInMemory", 74 | # didcomm.message 75 | "Attachment", 76 | "AttachmentDataBase64", 77 | "AttachmentDataJson", 78 | "AttachmentDataLinks", 79 | "FromPrior", 80 | "GenericMessage", 81 | "Message", 82 | # didcomm.pack_encrypted 83 | "pack_encrypted", 84 | "PackEncryptedConfig", 85 | "PackEncryptedParameters", 86 | "PackEncryptedResult", 87 | # didcomm.pack_plaintext 88 | "pack_plaintext", 89 | "PackPlaintextParameters", 90 | "PackPlaintextResult", 91 | # didcomm.pack_signed 92 | "pack_signed", 93 | "PackSignedParameters", 94 | "PackSignedResult", 95 | # didcomm.protocols.routing.forward 96 | "is_forward", 97 | "unpack_forward", 98 | "wrap_in_forward", 99 | "ForwardBody", 100 | "ForwardMessage", 101 | "ForwardPackResult", 102 | "ForwardResult", 103 | # didcomm.unpack 104 | "unpack", 105 | "Metadata", 106 | "UnpackConfig", 107 | "UnpackResult", 108 | # didcomm.secrets.secrets_resolver 109 | "Secret", 110 | "SecretsResolver", 111 | # didcomm.secrets.secrets_resolver_in_memory 112 | "SecretsResolverInMemory", 113 | ] 114 | -------------------------------------------------------------------------------- /didcomm/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/didcomm/common/__init__.py -------------------------------------------------------------------------------- /didcomm/common/algorithms.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from enum import Enum 3 | 4 | Algs = namedtuple("Algs", ["alg", "enc"]) 5 | 6 | 7 | class AnonCryptAlg(Enum): 8 | """ 9 | Algorithms for anonymous encryption. 10 | 11 | Attributes: 12 | A256CBC_HS512_ECDH_ES_A256KW: AES256-CBC + HMAC-SHA512 with a 512 bit key content encryption, 13 | ECDH-ES key agreement with A256KW key wrapping 14 | 15 | XC20P_ECDH_ES_A256KW: XChaCha20Poly1305 with a 256 bit key content encryption, 16 | ECDH-ES key agreement with A256KW key wrapping 17 | 18 | A256GCM_ECDH_ES_A256KW: XChaCha20Poly1305 with a 256 bit key content encryption, 19 | ECDH-ES key agreement with A256KW key wrapping 20 | """ 21 | 22 | A256CBC_HS512_ECDH_ES_A256KW = Algs(alg="ECDH-ES+A256KW", enc="A256CBC-HS512") 23 | XC20P_ECDH_ES_A256KW = Algs(alg="ECDH-ES+A256KW", enc="XC20P") 24 | A256GCM_ECDH_ES_A256KW = Algs(alg="ECDH-ES+A256KW", enc="A256GCM") 25 | 26 | 27 | class AuthCryptAlg(Enum): 28 | """ 29 | Algorithms for authentication encryption. 30 | 31 | Attributes: 32 | A256CBC_HS512_ECDH_1PU_A256KW: AES256-CBC + HMAC-SHA512 with a 512 bit key content encryption, 33 | ECDH-1PU key agreement with A256KW key wrapping 34 | """ 35 | 36 | A256CBC_HS512_ECDH_1PU_A256KW = Algs(alg="ECDH-1PU+A256KW", enc="A256CBC-HS512") 37 | 38 | 39 | class SignAlg(Enum): 40 | """ 41 | Algorithms for signature (non-repudiation) 42 | 43 | Attributes: 44 | ED25519: Elliptic curve digital signature with edwards curves Ed25519 and SHA-512 45 | ES256: Elliptic curve digital signature with NIST p-256 curve and SHA-256 46 | ES256K: Elliptic curve digital signature with Secp256k1 keys 47 | """ 48 | 49 | ED25519 = "EdDSA" 50 | ES256 = "ES256" 51 | ES256K = "ES256K" 52 | -------------------------------------------------------------------------------- /didcomm/common/resolvers.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from didcomm.did_doc.did_resolver import DIDResolver 4 | from didcomm.secrets.secrets_resolver import SecretsResolver 5 | 6 | 7 | @dataclass(frozen=True) 8 | class ResolversConfig: 9 | """ 10 | Resolvers configuration. 11 | 12 | Attributes: 13 | secrets_resolver (SecretsResolver): a _secrets resolver 14 | 15 | did_resolver (DIDResolver): a DID Doc resolver 16 | """ 17 | 18 | secrets_resolver: SecretsResolver 19 | did_resolver: DIDResolver 20 | -------------------------------------------------------------------------------- /didcomm/common/types.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from enum import Enum 5 | from typing import Dict, Any, Union, List 6 | from pydid.did import DID, DIDUrl 7 | 8 | JSON_OBJ = Dict[str, Any] 9 | JSON_VALUE = Union[type(None), str, int, bool, float, Dict, List] 10 | JSON = str 11 | JWK = JSON 12 | JWT = JSON 13 | JWS = JSON 14 | DID_URL = DIDUrl 15 | DID_OR_DID_URL = Union[DID, DID_URL] 16 | 17 | 18 | class VerificationMethodType: 19 | JSON_WEB_KEY_2020 = "JsonWebKey2020" 20 | X25519_KEY_AGREEMENT_KEY_2019 = "X25519KeyAgreementKey2019" 21 | ED25519_VERIFICATION_KEY_2018 = "Ed25519VerificationKey2018" 22 | X25519_KEY_AGREEMENT_KEY_2020 = "X25519KeyAgreementKey2020" 23 | ED25519_VERIFICATION_KEY_2020 = "Ed25519VerificationKey2020" 24 | # ECDSA_SECP_256K1_VERIFICATION_KEY_2019 = "EcdsaSecp256k1VerificationKey2019" - not supported now 25 | OTHER = "Other" 26 | 27 | 28 | class VerificationMaterialFormat(Enum): 29 | JWK = 1 30 | BASE58 = 2 31 | MULTIBASE = 3 32 | OTHER = 1000 33 | 34 | 35 | @dataclass 36 | class VerificationMaterial: 37 | format: VerificationMaterialFormat 38 | value: str 39 | 40 | 41 | class DIDDocServiceTypes(Enum): 42 | DID_COMM_MESSAGING = "DIDCommMessaging" 43 | 44 | 45 | class DIDCommMessageTypes(Enum): 46 | ENCRYPTED = "application/didcomm-encrypted+json" 47 | ENCRYPTED_SHORT = "didcomm-encrypted+json" 48 | SIGNED = "application/didcomm-signed+json" 49 | SIGNED_SHORT = "didcomm-signed+json" 50 | PLAINTEXT = "application/didcomm-plain+json" 51 | PLAINTEXT_SHORT = "didcomm-plain+json" 52 | 53 | 54 | DIDCommMessageMediaTypes = DIDCommMessageTypes 55 | 56 | # TODO 57 | # - replace DIDCommMessageTypes with DIDCommMessageMediaTypes 58 | # - rename DIDCommMessageProtocolTypes to DIDCommMessageTypes 59 | 60 | JWT_TYPE = "JWT" 61 | 62 | 63 | class DIDCommMessageProtocolTypes(Enum): 64 | FORWARD = "https://didcomm.org/routing/2.0/forward" 65 | 66 | 67 | class JOSEFields: 68 | # JOSE Header fields as defined in JWS and JWE specs 69 | # (RFCs 7515, 7516, 7518, 7519, 7797, 8225, 8555) 70 | # https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-header-parameters 71 | JOSE_ALG = "alg" 72 | JOSE_JKU = "jku" 73 | JOSE_JWK = "jwk" 74 | JOSE_KID = "kid" 75 | JOSE_X5U = "x5u" 76 | JOSE_X5C = "x5c" 77 | JOSE_X5T = "x5t" 78 | JOSE_X5T_S256 = "x5t#S256" 79 | JOSE_TYP = "typ" 80 | JOSE_CTY = "cty" 81 | JOSE_CRIT = "crit" 82 | JOSE_ENC = "enc" 83 | JOSE_ZIP = "zip" 84 | JOSE_EPK = "epk" 85 | JOSE_APU = "apu" 86 | JOSE_APV = "apv" 87 | JOSE_IV = "iv" 88 | JOSE_TAG = "tag" 89 | JOSE_P2S = "p2s" 90 | JOSE_P2C = "p2c" 91 | JOSE_ISS = "iss" 92 | JOSE_SUB = "sub" 93 | JOSE_AUD = "aud" 94 | JOSE_B64 = "b64" 95 | JOSE_PPT = "ppt" 96 | JOSE_URL = "url" 97 | JOSE_NONCE = "nonce" 98 | 99 | # JWS (non-header) fields 100 | # https://datatracker.ietf.org/doc/html/rfc7515#section-3.2 101 | JWS_PROTECTED = "protected" 102 | JWS_HEADER = "header" 103 | JWS_PAYLOAD = "payload" 104 | JWS_SIGNATURE = "signature" 105 | 106 | # JWE (non-heder) fields 107 | # https://datatracker.ietf.org/doc/html/rfc7516#section-3.2 108 | JWE_PROTECTED = JWS_PROTECTED 109 | JWE_UNPROTECTED = "unprotected" 110 | JWE_HEADER = JWS_HEADER 111 | JWE_ENCRYPTED_KEY = "encrypted_key" 112 | JWE_IV = JOSE_IV 113 | JWE_CIPHERTEXT = "ciphertext" 114 | JWE_TAG = JOSE_TAG 115 | JWE_AAD = "aad" 116 | -------------------------------------------------------------------------------- /didcomm/common/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Callable, Optional, Any 2 | 3 | 4 | # TODO check the same helper in standard lib 5 | # TODO test 6 | def search_first_in_iterable( 7 | it: Iterable, cond: Callable, not_found_default=None 8 | ) -> Optional[Any]: 9 | return next((el for el in it if cond(el)), not_found_default) 10 | -------------------------------------------------------------------------------- /didcomm/core/__init__.py: -------------------------------------------------------------------------------- 1 | from authlib.jose import JsonWebEncryption 2 | from authlib.jose.drafts import register_jwe_draft 3 | 4 | register_jwe_draft(JsonWebEncryption) 5 | -------------------------------------------------------------------------------- /didcomm/core/anoncrypt.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from authlib.jose import JsonWebEncryption 4 | 5 | from didcomm.common.algorithms import AnonCryptAlg, Algs 6 | from didcomm.common.resolvers import ResolversConfig 7 | from didcomm.common.types import DID_OR_DID_URL, DIDCommMessageTypes 8 | from didcomm.core.keys.anoncrypt_keys_selector import ( 9 | find_anoncrypt_pack_recipient_public_keys, 10 | find_anoncrypt_unpack_recipient_private_keys, 11 | ) 12 | from didcomm.core.serialization import dict_to_json_bytes 13 | from didcomm.core.types import EncryptResult, UnpackAnoncryptResult, Key 14 | from didcomm.core.utils import extract_key, get_jwe_alg, calculate_apv 15 | from didcomm.core.validation import validate_anoncrypt_jwe 16 | from didcomm.errors import MalformedMessageError, MalformedMessageCode 17 | 18 | 19 | def is_anoncrypted(msg: dict) -> bool: 20 | if "ciphertext" not in msg: 21 | return False 22 | alg = get_jwe_alg(msg) 23 | if alg is None: 24 | return False 25 | return alg.startswith("ECDH-ES") 26 | 27 | 28 | def anoncrypt(msg: dict, to: List[Key], alg: AnonCryptAlg) -> EncryptResult: 29 | msg = dict_to_json_bytes(msg) 30 | 31 | kids = [to_key.kid for to_key in to] 32 | keys = [to_key.key for to_key in to] 33 | 34 | header_obj = _build_header(to=to, alg=alg) 35 | 36 | jwe = JsonWebEncryption() 37 | res = jwe.serialize_json(header_obj, msg, keys) 38 | 39 | return EncryptResult(msg=res, to_kids=kids, to_keys=to) 40 | 41 | 42 | async def find_keys_and_anoncrypt( 43 | msg: dict, to: DID_OR_DID_URL, alg: AnonCryptAlg, resolvers_config: ResolversConfig 44 | ) -> EncryptResult: 45 | to_verification_methods = await find_anoncrypt_pack_recipient_public_keys( 46 | to, resolvers_config 47 | ) 48 | to_public_keys = [ 49 | Key(kid=to_vm.id, key=extract_key(to_vm)) for to_vm in to_verification_methods 50 | ] 51 | return anoncrypt(msg, to_public_keys, alg) 52 | 53 | 54 | async def unpack_anoncrypt( 55 | msg: dict, resolvers_config: ResolversConfig, decrypt_by_all_keys: bool 56 | ) -> UnpackAnoncryptResult: 57 | validate_anoncrypt_jwe(msg) 58 | 59 | to_kids = [r["header"]["kid"] for r in msg["recipients"]] 60 | 61 | unpack_res = None 62 | async for to_secret in find_anoncrypt_unpack_recipient_private_keys( 63 | to_kids, resolvers_config 64 | ): 65 | to_private_kid_and_key = (to_secret.kid, extract_key(to_secret)) 66 | try: 67 | jwe = JsonWebEncryption() 68 | res = jwe.deserialize_json(msg, to_private_kid_and_key) 69 | except Exception as exc: 70 | if decrypt_by_all_keys: 71 | raise MalformedMessageError( 72 | MalformedMessageCode.CAN_NOT_DECRYPT 73 | ) from exc 74 | continue 75 | 76 | if "payload" not in res: 77 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 78 | if "header" not in res or "protected" not in res["header"]: 79 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 80 | protected = res["header"]["protected"] 81 | if "alg" not in protected or "enc" not in protected: 82 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 83 | alg = AnonCryptAlg(Algs(alg=protected["alg"], enc=protected["enc"])) 84 | 85 | unpack_res = UnpackAnoncryptResult(msg=res["payload"], to_kids=to_kids, alg=alg) 86 | if not decrypt_by_all_keys: 87 | return unpack_res 88 | 89 | if unpack_res is None: 90 | raise MalformedMessageError(MalformedMessageCode.CAN_NOT_DECRYPT) 91 | return unpack_res 92 | 93 | 94 | def _build_header(to: List[Key], alg: AnonCryptAlg): 95 | kids = [to_key.kid for to_key in to] 96 | apv = calculate_apv(kids) 97 | protected = { 98 | "typ": DIDCommMessageTypes.ENCRYPTED.value, 99 | "alg": alg.value.alg, 100 | "enc": alg.value.enc, 101 | "apv": apv, 102 | } 103 | recipients = [{"header": {"kid": kid}} for kid in kids] 104 | return {"protected": protected, "recipients": recipients} 105 | -------------------------------------------------------------------------------- /didcomm/core/converters.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union, Any, Callable 2 | 3 | from didcomm.core.utils import id_generator_default, didcomm_id_generator_default 4 | 5 | 6 | def converter__id(_id: Optional[Union[Any, Callable]] = None): 7 | if _id is None: 8 | return id_generator_default() 9 | elif callable(_id): 10 | return _id() 11 | else: 12 | return _id 13 | 14 | 15 | def converter__didcomm_id(didcomm_id: Optional[Union[Any, Callable]] = None): 16 | if didcomm_id is None: 17 | return didcomm_id_generator_default() 18 | else: 19 | return converter__id(didcomm_id) 20 | -------------------------------------------------------------------------------- /didcomm/core/defaults.py: -------------------------------------------------------------------------------- 1 | from didcomm.common.algorithms import AnonCryptAlg, AuthCryptAlg 2 | 3 | 4 | DEF_ENC_ALG_AUTH: AuthCryptAlg = AuthCryptAlg.A256CBC_HS512_ECDH_1PU_A256KW 5 | DEF_ENC_ALG_ANON: AnonCryptAlg = AnonCryptAlg.XC20P_ECDH_ES_A256KW 6 | -------------------------------------------------------------------------------- /didcomm/core/keys/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/didcomm/core/keys/__init__.py -------------------------------------------------------------------------------- /didcomm/core/keys/anoncrypt_keys_selector.py: -------------------------------------------------------------------------------- 1 | from typing import List, AsyncGenerator, Any 2 | 3 | from didcomm.common.resolvers import ResolversConfig 4 | from didcomm.common.types import DID_OR_DID_URL, DID_URL, DID 5 | from didcomm.core.utils import get_did_and_optionally_kid, are_keys_compatible 6 | from didcomm.did_doc.did_doc import VerificationMethod 7 | from didcomm.errors import ( 8 | DIDDocNotResolvedError, 9 | DIDUrlNotFoundError, 10 | SecretNotFoundError, 11 | ) 12 | from didcomm.secrets.secrets_resolver import Secret 13 | 14 | 15 | async def find_anoncrypt_pack_recipient_public_keys( 16 | to_did_or_kid: DID_OR_DID_URL, resolvers_config: ResolversConfig 17 | ) -> List[VerificationMethod]: 18 | to_did, to_kid = get_did_and_optionally_kid(to_did_or_kid) 19 | if to_kid is None: 20 | return await _find_anoncrypt_pack_recipient_public_keys_by_did( 21 | to_did, resolvers_config 22 | ) 23 | return await _find_anoncrypt_pack_recipient_public_keys_by_kid( 24 | to_did, to_kid, resolvers_config 25 | ) 26 | 27 | 28 | # TODO: async generators require Python 3.6. 29 | # Think about alternative approach with the same properties that can work on Python 3.5 30 | async def find_anoncrypt_unpack_recipient_private_keys( 31 | to_kids: List[DID_URL], resolvers_config: ResolversConfig 32 | ) -> AsyncGenerator[Secret, Any]: 33 | secret_ids = await resolvers_config.secrets_resolver.get_keys(to_kids) 34 | if not secret_ids: 35 | raise DIDUrlNotFoundError( 36 | f"No secrets are found in secrets resolver for DID URLs: {to_kids}" 37 | ) 38 | 39 | found = False 40 | for secret_id in secret_ids: 41 | secret = await resolvers_config.secrets_resolver.get_key(secret_id) 42 | if secret is None: 43 | raise SecretNotFoundError( 44 | f"Secret `{secret_id}` is not found in secrets resolver" 45 | ) 46 | found = True 47 | yield secret 48 | 49 | if not found: 50 | raise DIDUrlNotFoundError( 51 | f"No secrets are found in secrets resolver for DID URLs: {secret_ids}" 52 | ) 53 | 54 | 55 | async def _find_anoncrypt_pack_recipient_public_keys_by_kid( 56 | to_did: DID, to_kid: DID_URL, resolvers_config: ResolversConfig 57 | ) -> List[VerificationMethod]: 58 | did_doc = await resolvers_config.did_resolver.resolve(to_did) 59 | if did_doc is None: 60 | raise DIDDocNotResolvedError(to_did) 61 | 62 | if not did_doc.key_agreement or to_kid not in did_doc.key_agreement: 63 | raise DIDUrlNotFoundError( 64 | f"DID URL `{to_kid}` is not found in keyAgreement verification relationships of DID `{to_did}`" 65 | ) 66 | 67 | verification_method = did_doc.get_verification_method(to_kid) 68 | if verification_method is None: 69 | raise DIDUrlNotFoundError(f"Verification method `{to_kid}` is not found") 70 | 71 | return [verification_method] 72 | 73 | 74 | async def _find_anoncrypt_pack_recipient_public_keys_by_did( 75 | to_did: DID, resolvers_config: ResolversConfig 76 | ) -> List[VerificationMethod]: 77 | did_doc = await resolvers_config.did_resolver.resolve(to_did) 78 | if did_doc is None: 79 | raise DIDDocNotResolvedError(to_did) 80 | 81 | kids = did_doc.key_agreement 82 | if not kids: 83 | raise DIDUrlNotFoundError( 84 | f"No keyAgreement verification relationships are found for DID `{to_did}`" 85 | ) 86 | 87 | # return only verification methods having the same type as the first one 88 | first_verification_method = did_doc.get_verification_method(kids[0]) 89 | if first_verification_method is None: 90 | raise DIDUrlNotFoundError(f"Verification method `{kids[0]}` is not found") 91 | 92 | verification_methods = [] 93 | for kid in kids: 94 | verification_method = did_doc.get_verification_method(kid) 95 | if verification_method is None: 96 | raise DIDUrlNotFoundError(f"Verification method `{kid}` is not found") 97 | if are_keys_compatible(first_verification_method, verification_method): 98 | verification_methods.append(verification_method) 99 | 100 | return verification_methods 101 | -------------------------------------------------------------------------------- /didcomm/core/keys/forward_next_keys_selector.py: -------------------------------------------------------------------------------- 1 | from didcomm.common.resolvers import ResolversConfig 2 | from didcomm.common.types import DID_OR_DID_URL 3 | from didcomm.core.utils import is_did_with_uri_fragment 4 | 5 | 6 | async def has_keys_for_forward_next( 7 | _next: DID_OR_DID_URL, resolvers_config: ResolversConfig 8 | ) -> bool: 9 | if is_did_with_uri_fragment(_next): 10 | next_kids = [_next] 11 | else: 12 | next_did_doc = await resolvers_config.did_resolver.resolve(_next) 13 | if next_did_doc is None: 14 | return False 15 | next_kids = next_did_doc.key_agreement 16 | 17 | secret_ids = await resolvers_config.secrets_resolver.get_keys(next_kids) 18 | return len(secret_ids) > 0 19 | -------------------------------------------------------------------------------- /didcomm/core/keys/sign_keys_selector.py: -------------------------------------------------------------------------------- 1 | from didcomm.common.resolvers import ResolversConfig 2 | from didcomm.common.types import DID_OR_DID_URL, DID_URL 3 | from didcomm.core.utils import get_did_and_optionally_kid, get_did 4 | from didcomm.did_doc.did_doc import VerificationMethod 5 | from didcomm.errors import ( 6 | DIDDocNotResolvedError, 7 | DIDUrlNotFoundError, 8 | SecretNotFoundError, 9 | ) 10 | from didcomm.secrets.secrets_resolver import Secret 11 | 12 | 13 | async def find_signing_key( 14 | frm_did_or_kid: DID_OR_DID_URL, resolvers_config: ResolversConfig 15 | ) -> Secret: 16 | frm_did, frm_kid = get_did_and_optionally_kid(frm_did_or_kid) 17 | 18 | if frm_kid is None: 19 | return await _find_signing_key_by_did(frm_did, resolvers_config) 20 | return await _find_signing_key_by_kid(frm_kid, resolvers_config) 21 | 22 | 23 | async def find_verification_key( 24 | frm_kid: DID_URL, resolvers_config: ResolversConfig 25 | ) -> VerificationMethod: 26 | did = get_did(frm_kid) 27 | 28 | did_doc = await resolvers_config.did_resolver.resolve(did) 29 | if did_doc is None: 30 | raise DIDDocNotResolvedError(did) 31 | 32 | if frm_kid not in did_doc.authentication: 33 | raise DIDUrlNotFoundError( 34 | f"DID URL `{frm_kid}` is not found in authentication verification relationships of DID `{did}`" 35 | ) 36 | 37 | verification_method = did_doc.get_verification_method(frm_kid) 38 | if verification_method is None: 39 | raise DIDUrlNotFoundError(f"Verification method `{frm_kid}` is not found") 40 | 41 | return verification_method 42 | 43 | 44 | async def _find_signing_key_by_kid( 45 | frm_kid: DID_URL, resolvers_config: ResolversConfig 46 | ) -> Secret: 47 | secret = await resolvers_config.secrets_resolver.get_key(frm_kid) 48 | if secret is None: 49 | raise SecretNotFoundError( 50 | f"Secret `{frm_kid}` is not found in secrets resolver" 51 | ) 52 | 53 | return secret 54 | 55 | 56 | async def _find_signing_key_by_did( 57 | frm_did: DID_OR_DID_URL, resolvers_config: ResolversConfig 58 | ) -> Secret: 59 | did_doc = await resolvers_config.did_resolver.resolve(frm_did) 60 | if did_doc is None: 61 | raise DIDDocNotResolvedError(frm_did) 62 | 63 | if not did_doc.authentication: 64 | raise DIDUrlNotFoundError( 65 | f"No authentication verification relationships are found for DID `{frm_did}`" 66 | ) 67 | 68 | secret_ids = await resolvers_config.secrets_resolver.get_keys( 69 | did_doc.authentication 70 | ) 71 | if not secret_ids: 72 | raise SecretNotFoundError( 73 | f"No secrets are found in secrets resolver for DID URLs: {did_doc.authentication}" 74 | ) 75 | 76 | kid = secret_ids[0] 77 | secret = await resolvers_config.secrets_resolver.get_key(kid) 78 | if secret is None: 79 | raise SecretNotFoundError(f"Secret `{kid}` is not found in secrets resolver") 80 | 81 | return secret 82 | -------------------------------------------------------------------------------- /didcomm/core/serialization.py: -------------------------------------------------------------------------------- 1 | from authlib.common.encoding import to_bytes, json_dumps, json_loads, to_unicode 2 | 3 | from didcomm.common.types import JSON 4 | from didcomm.errors import MalformedMessageError, MalformedMessageCode 5 | 6 | 7 | def dict_to_json_bytes(msg: dict) -> bytes: 8 | return to_bytes(json_dumps(msg)) 9 | 10 | 11 | def dict_to_json(msg: dict) -> JSON: 12 | return json_dumps(msg) 13 | 14 | 15 | def json_bytes_to_dict(json_bytes: bytes) -> dict: 16 | return json_str_to_dict(to_unicode(json_bytes)) 17 | 18 | 19 | def json_str_to_dict(json_str: JSON) -> dict: 20 | try: 21 | json_dict = json_loads(json_str) 22 | except Exception as exc: 23 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) from exc 24 | 25 | if not isinstance(json_dict, dict): # in case of a primitive value 26 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 27 | 28 | return json_dict 29 | -------------------------------------------------------------------------------- /didcomm/core/sign.py: -------------------------------------------------------------------------------- 1 | from authlib.jose import JsonWebSignature 2 | from authlib.jose.errors import BadSignatureError 3 | 4 | from didcomm.common.resolvers import ResolversConfig 5 | from didcomm.common.types import DID_OR_DID_URL, DIDCommMessageTypes 6 | from didcomm.core.keys.sign_keys_selector import find_signing_key, find_verification_key 7 | from didcomm.core.serialization import dict_to_json_bytes 8 | from didcomm.core.types import SignResult, UnpackSignResult 9 | from didcomm.core.utils import extract_key, extract_sign_alg 10 | from didcomm.core.validation import validate_jws 11 | from didcomm.errors import MalformedMessageError, MalformedMessageCode 12 | 13 | 14 | def is_signed(msg: dict) -> bool: 15 | return "payload" in msg 16 | 17 | 18 | async def sign( 19 | msg: dict, sign_frm: DID_OR_DID_URL, resolvers_config: ResolversConfig 20 | ) -> SignResult: 21 | msg = dict_to_json_bytes(msg) 22 | 23 | jws = JsonWebSignature() 24 | 25 | secret = await find_signing_key(sign_frm, resolvers_config) 26 | private_key = extract_key(secret) 27 | alg = extract_sign_alg(secret) 28 | 29 | protected = {"typ": DIDCommMessageTypes.SIGNED.value, "alg": alg.value} 30 | 31 | header = {"kid": secret.kid} 32 | 33 | header_objs = [{"protected": protected, "header": header}] 34 | 35 | res = jws.serialize_json(header_objs, msg, private_key) 36 | 37 | return SignResult(msg=res, sign_frm_kid=secret.kid) 38 | 39 | 40 | async def unpack_sign(msg: dict, resolvers_config: ResolversConfig) -> UnpackSignResult: 41 | validate_jws(msg) 42 | 43 | sign_frm_kid = msg["signatures"][0]["header"]["kid"] 44 | sign_frm_verification_method = await find_verification_key( 45 | sign_frm_kid, resolvers_config 46 | ) 47 | public_key = extract_key(sign_frm_verification_method) 48 | alg = extract_sign_alg(sign_frm_verification_method) 49 | 50 | try: 51 | jws = JsonWebSignature() 52 | jws_object = jws.deserialize_json(msg, public_key) 53 | except BadSignatureError as exc: 54 | raise MalformedMessageError(MalformedMessageCode.INVALID_SIGNATURE) from exc 55 | except Exception as exc: 56 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) from exc 57 | 58 | return UnpackSignResult(msg=jws_object.payload, sign_frm_kid=sign_frm_kid, alg=alg) 59 | -------------------------------------------------------------------------------- /didcomm/core/types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List, Optional, Callable 3 | 4 | from authlib.jose.rfc7517 import AsymmetricKey 5 | 6 | from didcomm.common.algorithms import SignAlg, AnonCryptAlg, AuthCryptAlg 7 | from didcomm.common.types import DID_URL, DID_OR_DID_URL 8 | 9 | DIDCOMM_ORG_DOMAIN = "didcomm.org" 10 | 11 | 12 | class JWMFields: 13 | # https://datatracker.ietf.org/doc/html/draft-looker-jwm-01#page-10 14 | ID = "id" 15 | TYPE = "type" 16 | BODY = "body" 17 | TO = "to" 18 | FROM = "from" 19 | THREAD_ID = "thread_id" 20 | CREATED_TIME = "created_time" 21 | EXPIRES_TIME = "expires_time" 22 | REPLY_URL = "reply_url" 23 | REPLY_TO = "reply_to" 24 | 25 | 26 | class DIDCommFields: 27 | # https://identity.foundation/didcomm-messaging/spec/#message-headers 28 | ID = JWMFields.ID 29 | TYPE = JWMFields.TYPE 30 | TYP = "typ" 31 | TO = JWMFields.TO 32 | FROM = JWMFields.FROM 33 | BODY = JWMFields.BODY 34 | THID = "thid" 35 | PTHID = "pthid" 36 | CREATED_TIME = "created_time" 37 | EXPIRES_TIME = "expires_time" 38 | # https://identity.foundation/didcomm-messaging/spec/#did-rotation 39 | FROM_PRIOR = "from_prior" 40 | # https://identity.foundation/didcomm-messaging/spec/#acks 41 | PLEASE_ACK = "please_ack" 42 | ACK = "ack" 43 | # https://identity.foundation/didcomm-messaging/spec/#messages 44 | NEXT = "next" 45 | # https://identity.foundation/didcomm-messaging/spec/#reference-2 46 | ATTACHMENTS = "attachments" 47 | 48 | 49 | @dataclass(frozen=True) 50 | class Key: 51 | kid: DID_URL 52 | key: AsymmetricKey 53 | 54 | 55 | @dataclass(frozen=True) 56 | class SignResult: 57 | msg: dict 58 | sign_frm_kid: DID_URL 59 | 60 | 61 | @dataclass(frozen=True) 62 | class EncryptResult: 63 | msg: dict 64 | to_kids: List[DID_OR_DID_URL] 65 | to_keys: List[Key] 66 | from_kid: Optional[DID_OR_DID_URL] = None 67 | 68 | 69 | @dataclass(frozen=True) 70 | class UnpackSignResult: 71 | msg: bytes 72 | sign_frm_kid: DID_URL 73 | alg: SignAlg 74 | 75 | 76 | @dataclass(frozen=True) 77 | class UnpackAnoncryptResult: 78 | msg: bytes 79 | to_kids: List[DID_URL] 80 | alg: AnonCryptAlg 81 | 82 | 83 | @dataclass(frozen=True) 84 | class UnpackAuthcryptResult: 85 | msg: bytes 86 | to_kids: List[DID_URL] 87 | frm_kid: DID_URL 88 | alg: AuthCryptAlg 89 | 90 | 91 | DIDCommGeneratorType = Callable[[Optional[DID_OR_DID_URL]], str] 92 | -------------------------------------------------------------------------------- /didcomm/core/validation.py: -------------------------------------------------------------------------------- 1 | from authlib.common.encoding import to_bytes, urlsafe_b64decode, to_unicode 2 | 3 | from didcomm.core.utils import ( 4 | is_did_with_uri_fragment, 5 | parse_base64url_encoded_json, 6 | calculate_apv, 7 | ) 8 | from didcomm.errors import MalformedMessageError, MalformedMessageCode 9 | 10 | 11 | def validate_jws(msg: dict): 12 | if ( 13 | "signatures" not in msg 14 | or not msg["signatures"] 15 | or "header" not in msg["signatures"][0] 16 | or "kid" not in msg["signatures"][0]["header"] 17 | ): 18 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 19 | 20 | 21 | def validate_anoncrypt_jwe(msg: dict): 22 | # 1. Validate recipient unprotected header 23 | if "recipients" not in msg: 24 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 25 | for r in msg["recipients"]: 26 | if "header" not in r or "kid" not in r["header"]: 27 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 28 | 29 | # 2. Decode protected header 30 | protected_header = _get_protected_header(msg) 31 | 32 | # 3. Check apv 33 | _check_apv(protected_header, msg["recipients"]) 34 | 35 | return protected_header 36 | 37 | 38 | def validate_authcrypt_jwe(msg: dict): 39 | # 1. Validate recipient unprotected header 40 | if "recipients" not in msg: 41 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 42 | for r in msg["recipients"]: 43 | if "header" not in r or "kid" not in r["header"]: 44 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 45 | if not is_did_with_uri_fragment(r["header"]["kid"]): 46 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 47 | 48 | # 2. Decode protected header 49 | protected_header = _get_protected_header(msg) 50 | 51 | # 3. Check apu 52 | if "apu" not in protected_header: 53 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 54 | try: 55 | apu = to_unicode(urlsafe_b64decode(to_bytes(protected_header["apu"]))) 56 | except Exception as exc: 57 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) from exc 58 | if not is_did_with_uri_fragment(apu): 59 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 60 | 61 | # 4. Check skid 62 | if "skid" in protected_header and protected_header["skid"] != apu: 63 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 64 | 65 | # 5. Check apv 66 | _check_apv(protected_header, msg["recipients"]) 67 | 68 | return protected_header 69 | 70 | 71 | def _get_protected_header(jwe: dict): 72 | if "protected" not in jwe: 73 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 74 | try: 75 | protected = parse_base64url_encoded_json(jwe["protected"]) 76 | except Exception as exc: 77 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) from exc 78 | return protected 79 | 80 | 81 | def _check_apv(protected_header: dict, recipients: list): 82 | if "apv" not in protected_header: 83 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 84 | kids = [r["header"]["kid"] for r in recipients] 85 | expected_apv = calculate_apv(kids) 86 | if protected_header["apv"] != expected_apv: 87 | raise MalformedMessageError(MalformedMessageCode.INVALID_MESSAGE) 88 | -------------------------------------------------------------------------------- /didcomm/core/validators.py: -------------------------------------------------------------------------------- 1 | import attr 2 | from typing import Callable, Any, Optional 3 | from packaging.specifiers import SpecifierSet 4 | from urllib.parse import urlparse 5 | from pathlib import Path 6 | 7 | from didcomm.errors import DIDCommValueError 8 | from didcomm.core.types import DIDCOMM_ORG_DOMAIN 9 | from didcomm.core.utils import is_did, is_did_with_uri_fragment, is_did_or_did_url 10 | 11 | 12 | # TODO TEST 13 | def _attr_validator_wrapper(attr_validator): 14 | def _f(instance, attribute, value): 15 | try: 16 | attr_validator(instance, attribute, value) 17 | except Exception as exc: 18 | raise DIDCommValueError(str(exc)) from exc 19 | 20 | return _f 21 | 22 | 23 | def validator__optional(validator: Callable) -> Callable: 24 | return _attr_validator_wrapper(attr.validators.optional(validator)) 25 | 26 | 27 | # TODO TEST 28 | def validator__instance_of(classinfo) -> Callable: 29 | return _attr_validator_wrapper(attr.validators.instance_of(classinfo)) 30 | 31 | 32 | def validator__and_(*validators: Callable): 33 | return _attr_validator_wrapper(attr.validators.and_(*validators)) 34 | 35 | 36 | # TODO TEST 37 | def validator__in_(options) -> Callable: 38 | return _attr_validator_wrapper(attr.validators.in_(options)) 39 | 40 | 41 | def validator__not_in_(options) -> Callable: 42 | return _attr_validator_wrapper(attr.validators.not_(attr.validators.in_(options))) 43 | 44 | 45 | # TODO TEST 46 | def validator__deep_iterable(member_validator: Callable, iterable_validator=None): 47 | return _attr_validator_wrapper( 48 | attr.validators.deep_iterable(member_validator, iterable_validator) 49 | ) 50 | 51 | 52 | # TODO TEST 53 | def validator__deep_mapping( 54 | key_validator: Callable, 55 | value_validator: Callable, 56 | mapping_validator: Callable = None, 57 | ): 58 | return _attr_validator_wrapper( 59 | attr.validators.deep_mapping(key_validator, value_validator, mapping_validator) 60 | ) 61 | 62 | 63 | # TODO TEST 64 | def validator__didcomm_protocol_mturi( 65 | p_name: str, p_version_specifier: SpecifierSet, p_msg_t: str 66 | ) -> Callable: 67 | # TODO strict check as per 68 | # https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0003-protocols/README.md#mturi 69 | def _f(instance, attribute, value): 70 | parsed = urlparse(value) 71 | path = Path(parsed.path) 72 | 73 | if not ( 74 | parsed.scheme == "https" 75 | and parsed.netloc == DIDCOMM_ORG_DOMAIN 76 | # e.g. ('/', 'routing', '2.0.0', 'forward') 77 | and len(path.parts) == 4 78 | ): 79 | raise DIDCommValueError(f"not a {DIDCOMM_ORG_DOMAIN} protocol: '{value}'") 80 | 81 | if not ( 82 | path.parent.parent.name == p_name 83 | and path.parent.name in p_version_specifier 84 | ): 85 | raise DIDCommValueError(f"unexpected protocol in '{value}'") 86 | 87 | if path.name != p_msg_t: 88 | raise DIDCommValueError(f"unexpected message type in '{value}'") 89 | 90 | return _f 91 | 92 | 93 | # TODO TEST 94 | def validator__check_f( 95 | check_f: Callable[[Any], bool], error_msg: Optional[str] = "is unacceptable" 96 | ) -> Callable: 97 | def _f(instance, attribute, value): 98 | exc_msg = f"'{attribute.name}': value '{value}' {error_msg}" 99 | try: 100 | if not check_f(value): 101 | raise DIDCommValueError(exc_msg) 102 | except Exception as cause: 103 | raise DIDCommValueError(exc_msg) from cause 104 | 105 | return _f 106 | 107 | 108 | # TODO TEST 109 | def validator__did() -> Callable: 110 | return validator__check_f(is_did, "is not a did") 111 | 112 | 113 | # TODO TEST 114 | def validator__did_url(instance, attribute, value) -> None: 115 | validator__check_f(is_did_with_uri_fragment, "is not a did url with a fragment")( 116 | instance, attribute, value 117 | ) 118 | 119 | 120 | # TODO TEST 121 | def validator__did_or_did_url(instance, attribute, value) -> None: 122 | validator__check_f(is_did_or_did_url, "is neither a did nor a did url")( 123 | instance, attribute, value 124 | ) 125 | -------------------------------------------------------------------------------- /didcomm/did_doc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/didcomm/did_doc/__init__.py -------------------------------------------------------------------------------- /didcomm/did_doc/did_doc.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional 4 | 5 | from pydid import DIDCommService 6 | from pydid import VerificationMethod 7 | from pydid.doc import DIDDocument 8 | 9 | from didcomm.common.types import DID_URL 10 | from didcomm.common.utils import search_first_in_iterable 11 | 12 | 13 | class DIDDoc(DIDDocument): 14 | def get_verification_method(self, id: DID_URL) -> Optional[VerificationMethod]: 15 | """ 16 | Returns the verification method with the given identifier. 17 | 18 | :param id: an identifier of a verification method 19 | :return: the verification method or None of there is no one for the given identifier 20 | """ 21 | return ( 22 | search_first_in_iterable(self.verification_method, lambda x: x.id == id) 23 | if self.verification_method 24 | else None 25 | ) 26 | 27 | def get_didcomm_service(self, id: str) -> Optional[DIDCommService]: 28 | """ 29 | Returns DID Document service endpoint with the given identifier. 30 | 31 | :param id: an identifier of a service endpoint 32 | :return: the service endpoint or None of there is no one for the given identifier 33 | """ 34 | return ( 35 | search_first_in_iterable(self.service, lambda x: x.id == id) 36 | if self.service 37 | else None 38 | ) 39 | -------------------------------------------------------------------------------- /didcomm/did_doc/did_resolver.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List, Optional 3 | 4 | from didcomm.common.types import DID 5 | from didcomm.did_doc.did_doc import DIDDoc 6 | 7 | 8 | class DIDResolver(ABC): 9 | """DID Resolver to resolver a DID to a DID DOC.""" 10 | 11 | @abstractmethod 12 | async def resolve(self, did: DID) -> Optional[DIDDoc]: 13 | """ 14 | Resolves a DID by the given DID or DID URL. 15 | 16 | :param did: a DID to be resolved 17 | :return: an instance of resolved DID DOC or None if the DID can not be resolved by the given resolver 18 | """ 19 | pass 20 | 21 | 22 | class ChainedDIDResolver(DIDResolver): 23 | """ 24 | A sample implementation of a DID Resolver. 25 | 26 | Multiple resolvers can be registered here. 27 | DID resolution will be done by asking every registered resolver to resolve a DID (in the order they are passed) 28 | until a DID DOC is resolved. 29 | """ 30 | 31 | def __init__(self, did_resolvers: List[DIDResolver]): 32 | self.__did_resolvers = did_resolvers 33 | 34 | async def resolve(self, did: DID) -> Optional[DIDDoc]: 35 | """ 36 | Resolves a DID by asking every registered resolver to resolve a DID 37 | (in the order they are passed to the constructor) 38 | until a DID DOC is resolved. 39 | 40 | :param did: a DID to be resolved 41 | :return: an instance of resolved DID DOC or None if it can not be resolved by all of the registered resolvers. 42 | """ 43 | 44 | for did_resolver in self.__did_resolvers: 45 | did_doc = await did_resolver.resolve(did) 46 | if did_doc is not None: 47 | return did_doc 48 | return None 49 | -------------------------------------------------------------------------------- /didcomm/did_doc/did_resolver_in_memory.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from didcomm.common.types import DID 4 | from didcomm.did_doc.did_doc import DIDDoc 5 | from didcomm.did_doc.did_resolver import DIDResolver 6 | 7 | 8 | class DIDResolverInMemory(DIDResolver): 9 | def __init__(self, did_docs: List[DIDDoc]): 10 | self._did_docs = {did_doc.id: did_doc for did_doc in did_docs} 11 | 12 | async def resolve(self, did: DID) -> Optional[DIDDoc]: 13 | return self._did_docs.get(did) 14 | -------------------------------------------------------------------------------- /didcomm/errors.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from didcomm.common.types import DID 4 | 5 | 6 | class DIDCommError(Exception): 7 | pass 8 | 9 | 10 | class DIDCommValueError(DIDCommError, ValueError): 11 | pass 12 | 13 | 14 | class DIDDocNotResolvedError(DIDCommError): 15 | def __init__(self, did: DID): 16 | self.message = f"DID `{did}` is not found in DID resolver" 17 | 18 | 19 | class DIDUrlNotFoundError(DIDCommError): 20 | pass 21 | 22 | 23 | class SecretNotFoundError(DIDCommError): 24 | pass 25 | 26 | 27 | class IncompatibleCryptoError(DIDCommError): 28 | def __init__(self): 29 | self.message = "Sender and recipient keys corresponding to provided parameters are incompatible to each other" 30 | 31 | 32 | class InvalidDIDDocError(DIDCommValueError): 33 | pass 34 | 35 | 36 | class MalformedMessageCode(Enum): 37 | CAN_NOT_DECRYPT = 1 38 | INVALID_SIGNATURE = 2 39 | INVALID_PLAINTEXT = 3 40 | INVALID_MESSAGE = 4 41 | NOT_SUPPORTED_FWD_PROTOCOL = 5 42 | 43 | 44 | class MalformedMessageError(DIDCommError): 45 | def __init__(self, code: MalformedMessageCode, message: str = None): 46 | self.code = code 47 | 48 | if message is not None: 49 | self.message = message 50 | else: 51 | if self.code == MalformedMessageCode.CAN_NOT_DECRYPT: 52 | self.message = "DIDComm message cannot be decrypted" 53 | elif self.code == MalformedMessageCode.INVALID_SIGNATURE: 54 | self.message = "Signature is invalid" 55 | elif self.code == MalformedMessageCode.INVALID_PLAINTEXT: 56 | self.message = "Plaintext is invalid" 57 | elif self.code == MalformedMessageCode.INVALID_MESSAGE: 58 | self.message = "DIDComm message is invalid" 59 | elif self.code == MalformedMessageCode.NOT_SUPPORTED_FWD_PROTOCOL: 60 | self.message = "Not supported Forward protocol" 61 | -------------------------------------------------------------------------------- /didcomm/pack_plaintext.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Optional, Union 5 | 6 | from didcomm.common.resolvers import ResolversConfig 7 | from didcomm.common.types import JSON, DID_URL, JSON_OBJ 8 | from didcomm.core.serialization import dict_to_json 9 | from didcomm.core.from_prior import pack_from_prior_in_place 10 | from didcomm.message import Message 11 | 12 | 13 | async def pack_plaintext( 14 | resolvers_config: ResolversConfig, 15 | message: Union[Message, JSON_OBJ], 16 | pack_params: Optional[PackPlaintextParameters] = None, 17 | ) -> PackPlaintextResult: 18 | """ 19 | Produces `DIDComm Plaintext Messages` 20 | https://identity.foundation/didcomm-messaging/spec/#didcomm-plaintext-messages. 21 | 22 | A DIDComm message in its plaintext form that 23 | - is not packaged into any protective envelope 24 | - lacks confidentiality and integrity guarantees 25 | - repudiable 26 | 27 | They are therefore not normally transported across security boundaries. 28 | 29 | However, this may be a helpful format to inspect in debuggers, since it exposes underlying semantics, 30 | and it is the format used in the DIDComm spec to give examples of headers and other internals. 31 | Depending on ambient security, plaintext may or may not be an appropriate format for DIDComm data at rest. 32 | 33 | :param resolvers_config: secrets and DIDDoc resolvers 34 | :param message: The message to be packed into a DIDComm message 35 | :param pack_params: Optional parameters for pack 36 | 37 | :raises DIDNotResolvedError: If a DID or DID URL (key ID) can not be resolved or not found 38 | :raises SecretNotResolvedError: If there is no secret for the given DID or DID URL (key ID) 39 | 40 | :return: PackPlaintextResult 41 | """ 42 | pack_params = pack_params or PackPlaintextParameters() 43 | 44 | if isinstance(message, Message): 45 | message = message.as_dict() 46 | 47 | from_prior_issuer_kid = await pack_from_prior_in_place( 48 | message, 49 | resolvers_config, 50 | pack_params.from_prior_issuer_kid, 51 | ) 52 | 53 | packed_msg = dict_to_json(message) 54 | 55 | return PackPlaintextResult(packed_msg, from_prior_issuer_kid) 56 | 57 | 58 | @dataclass(frozen=True) 59 | class PackPlaintextResult: 60 | """ 61 | Result of pack operation. 62 | 63 | Attributes: 64 | packed_msg (str): A packed message as a JSON string 65 | from_prior_issuer_kid (DID_URL): Identifier (DID URL) of issuer key used for signing from_prior. 66 | None if the message does not contain from_prior. 67 | """ 68 | 69 | packed_msg: JSON 70 | from_prior_issuer_kid: Optional[DID_URL] = None 71 | 72 | 73 | @dataclass 74 | class PackPlaintextParameters: 75 | """ 76 | Optional parameters for pack. 77 | 78 | Attributes: 79 | from_prior_issuer_kid (DID_URL): If from_prior is specified in the source message, 80 | this field can explicitly specify which key to use for signing from_prior 81 | in the packed message 82 | """ 83 | 84 | from_prior_issuer_kid: Optional[DID_URL] = None 85 | -------------------------------------------------------------------------------- /didcomm/pack_signed.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Optional, Union 5 | 6 | from didcomm.common.resolvers import ResolversConfig 7 | from didcomm.common.types import JSON, DID_OR_DID_URL, DID_URL, JSON_OBJ 8 | from didcomm.core.serialization import dict_to_json 9 | from didcomm.core.sign import sign 10 | from didcomm.core.utils import is_did 11 | from didcomm.errors import DIDCommValueError 12 | from didcomm.core.from_prior import pack_from_prior_in_place 13 | from didcomm.message import Message 14 | 15 | 16 | async def pack_signed( 17 | resolvers_config: ResolversConfig, 18 | message: Union[Message, JSON_OBJ], 19 | sign_frm: DID_OR_DID_URL, 20 | pack_params: Optional[PackSignedParameters] = None, 21 | ) -> PackSignedResult: 22 | """ 23 | Produces `DIDComm Signed Message` 24 | https://identity.foundation/didcomm-messaging/spec/#didcomm-signed-message. 25 | 26 | The method signs (non-repudiation added) the message keeping it unencrypted. 27 | 28 | Signed messages are only necessary when 29 | - the origin of plaintext must be provable to third parties 30 | - or the sender can’t be proven to the recipient by authenticated encryption because the recipient 31 | is not known in advance (e.g., in a broadcast scenario). 32 | 33 | Adding a signature when one is not needed can degrade rather than enhance security because it 34 | relinquishes the sender’s ability to speak off the record. 35 | 36 | Signing is done as follows: 37 | - Signing is done via the keys from the `authentication` verification relationship in the DID Doc 38 | for the DID to be used for signing 39 | - If `sign_frm` is a DID, then the first sender's `authentication` verification method is used for which 40 | a private key in the _secrets resolver is found 41 | - If `sign_frm` is a key ID, then the sender's `authentication` verification method identified by the given key ID is used. 42 | 43 | :param resolvers_config: secrets and DIDDoc resolvers 44 | :param message: The message to be packed into a DIDComm message 45 | :param sign_frm: DID or key ID the sender uses for signing. 46 | :param pack_params: Optional parameters for pack 47 | 48 | :raises DIDDocNotResolvedError: If a DID can not be resolved to a DID Doc. 49 | :raises DIDUrlNotFoundError: If a DID URL (for example a key ID) is not found within a DID Doc 50 | :raises SecretNotFoundError: If there is no secret for the given DID or DID URL (key ID) 51 | :raises DIDCommValueError: If invalid input is provided. 52 | 53 | :return: PackSignedResult 54 | """ 55 | pack_params = pack_params or PackSignedParameters() 56 | 57 | __validate(sign_frm) 58 | 59 | if isinstance(message, Message): 60 | message = message.as_dict() 61 | 62 | from_prior_issuer_kid = await pack_from_prior_in_place( 63 | message, 64 | resolvers_config, 65 | pack_params.from_prior_issuer_kid, 66 | ) 67 | 68 | sign_result = await sign(message, sign_frm, resolvers_config) 69 | packed_msg = dict_to_json(sign_result.msg) 70 | 71 | return PackSignedResult( 72 | packed_msg=packed_msg, 73 | sign_from_kid=sign_result.sign_frm_kid, 74 | from_prior_issuer_kid=from_prior_issuer_kid, 75 | ) 76 | 77 | 78 | @dataclass(frozen=True) 79 | class PackSignedResult: 80 | """ 81 | Result of pack operation. 82 | 83 | Attributes: 84 | packed_msg (str): A packed message as a JSON string 85 | sign_from_kid (DID_URL): Identifier (DID URL) of sender key used for message signing 86 | from_prior_issuer_kid (DID_URL): Identifier (DID URL) of issuer key used for signing from_prior. 87 | None if the message does not contain from_prior. 88 | """ 89 | 90 | packed_msg: JSON 91 | sign_from_kid: DID_URL 92 | from_prior_issuer_kid: Optional[DID_URL] = None 93 | 94 | 95 | @dataclass 96 | class PackSignedParameters: 97 | """ 98 | Optional parameters for pack. 99 | 100 | Attributes: 101 | from_prior_issuer_kid (DID_URL): If from_prior is specified in the source message, 102 | this field can explicitly specify which key to use for signing from_prior 103 | in the packed message 104 | """ 105 | 106 | from_prior_issuer_kid: Optional[DID_URL] = None 107 | 108 | 109 | def __validate(sign_frm: DID_OR_DID_URL): 110 | if not is_did(sign_frm): 111 | raise DIDCommValueError( 112 | f"`sign_from` value is not a valid DID of DID URL: {sign_frm}" 113 | ) 114 | -------------------------------------------------------------------------------- /didcomm/protocols/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/didcomm/protocols/__init__.py -------------------------------------------------------------------------------- /didcomm/protocols/routing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/didcomm/protocols/routing/__init__.py -------------------------------------------------------------------------------- /didcomm/secrets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/didcomm/secrets/__init__.py -------------------------------------------------------------------------------- /didcomm/secrets/secrets_resolver.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from dataclasses import dataclass 3 | from typing import List, Optional 4 | 5 | from didcomm.common.types import DID_URL, VerificationMethodType, VerificationMaterial 6 | 7 | 8 | @dataclass 9 | class Secret: 10 | """ 11 | A secret (private key) abstraction. 12 | 13 | Attributes: 14 | kid (str): a key ID identifying a secret (private key). 15 | Must have the same value, as key ID ('id' field) of the corresponding method in DID Doc containing a public key. 16 | 17 | type (VerificationMethodType): secret (private key) type. 18 | Must have the same value, as type ('type' field as VerificationMethodType enum) of the corresponding method in DID Doc containing a public key. 19 | 20 | verification_material (VerificationMaterial): A verification material representing a private key. 21 | """ 22 | 23 | kid: DID_URL 24 | type: VerificationMethodType 25 | verification_material: VerificationMaterial 26 | 27 | 28 | class SecretsResolver(ABC): 29 | """Resolves _secrets such as private keys to be used for signing and encryption.""" 30 | 31 | @abstractmethod 32 | async def get_key(self, kid: DID_URL) -> Optional[Secret]: 33 | """ 34 | Finds d private key identified by the given key ID. 35 | 36 | :param kid: the key ID identifying a private key 37 | :return: a private key or None of there is no key for the given key ID 38 | """ 39 | pass 40 | 41 | @abstractmethod 42 | async def get_keys(self, kids: List[DID_URL]) -> List[DID_URL]: 43 | """ 44 | Find all private keys that have one of the given key IDs. 45 | Return keys only for key IDs for which a key is present. 46 | 47 | :param kids: the key IDs find private keys for 48 | :return: a possible empty list of all private keys that have one of the given keyIDs. 49 | """ 50 | pass 51 | -------------------------------------------------------------------------------- /didcomm/secrets/secrets_resolver_demo.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from typing import List, Optional 4 | 5 | from didcomm.common.types import DID_URL 6 | from didcomm.secrets.secrets_resolver import Secret 7 | from didcomm.secrets.secrets_resolver_editable import SecretsResolverEditable 8 | from didcomm.secrets.secrets_util import jwk_to_secret, secret_to_jwk_dict 9 | 10 | 11 | class SecretsResolverDemo(SecretsResolverEditable): 12 | def __init__(self, file_path="secrets.json"): 13 | self.file_path = file_path 14 | 15 | if not Path(file_path).exists(): 16 | self._secrets = {} 17 | self._save() 18 | 19 | with open(self.file_path) as f: 20 | jwk_keys = json.load(f) 21 | self._secrets = {jwk_key["kid"]: jwk_to_secret(jwk_key) for jwk_key in jwk_keys} 22 | 23 | def _save(self): 24 | with open(self.file_path, "w") as f: 25 | secrets_as_jwk = [secret_to_jwk_dict(s) for s in self._secrets.values()] 26 | json.dump(secrets_as_jwk, f) 27 | 28 | async def add_key(self, secret: Secret): 29 | self._secrets[secret.kid] = secret 30 | self._save() 31 | 32 | async def get_kids(self) -> List[str]: 33 | return list(self._secrets.keys()) 34 | 35 | async def get_key(self, kid: DID_URL) -> Optional[Secret]: 36 | return self._secrets.get(kid) 37 | 38 | async def get_keys(self, kids: List[DID_URL]) -> List[DID_URL]: 39 | return [s.kid for s in self._secrets.values() if s.kid in kids] 40 | -------------------------------------------------------------------------------- /didcomm/secrets/secrets_resolver_editable.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | from typing import List 3 | 4 | from didcomm.secrets.secrets_resolver import SecretsResolver, Secret 5 | 6 | 7 | class SecretsResolverEditable(SecretsResolver): 8 | @abstractmethod 9 | async def add_key(self, secret: Secret): 10 | pass 11 | 12 | @abstractmethod 13 | async def get_kids(self) -> List[str]: 14 | pass 15 | -------------------------------------------------------------------------------- /didcomm/secrets/secrets_resolver_in_memory.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from didcomm.common.types import DID_URL 4 | from didcomm.secrets.secrets_resolver import SecretsResolver, Secret 5 | 6 | 7 | class SecretsResolverInMemory(SecretsResolver): 8 | def __init__(self, secrets: List[Secret]): 9 | self._secrets = {secret.kid: secret for secret in secrets} 10 | 11 | async def get_key(self, kid: DID_URL) -> Optional[Secret]: 12 | return self._secrets.get(kid) 13 | 14 | async def get_keys(self, kids: List[DID_URL]) -> List[DID_URL]: 15 | return [s.kid for s in self._secrets.values() if s.kid in kids] 16 | -------------------------------------------------------------------------------- /didcomm/secrets/secrets_util.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Dict, Tuple 3 | 4 | from authlib.common.encoding import json_dumps 5 | from authlib.jose import OKPKey 6 | 7 | from didcomm.common.types import ( 8 | VerificationMethodType, 9 | VerificationMaterial, 10 | VerificationMaterialFormat, 11 | ) 12 | from didcomm.errors import DIDCommValueError 13 | from didcomm.secrets.secrets_resolver import Secret 14 | 15 | 16 | def jwk_to_secret(jwk: dict) -> Secret: 17 | """ 18 | Converts a JWK dict to a new Secret instance. 19 | 20 | :param jwk: JWK as dict 21 | :return: a new Secret instance 22 | """ 23 | return Secret( 24 | kid=jwk["kid"], 25 | type=VerificationMethodType.JSON_WEB_KEY_2020, 26 | verification_material=VerificationMaterial( 27 | format=VerificationMaterialFormat.JWK, value=json_dumps(jwk) 28 | ), 29 | ) 30 | 31 | 32 | def secret_to_jwk_dict(secret: Secret) -> Dict: 33 | """ 34 | Converts a Secret to a JWK dict. Should be used for Secrets in JWK format only. 35 | 36 | :param secret: s Secret to be converted 37 | :return: JWK dict 38 | """ 39 | # assume JWK secrets only 40 | if secret.verification_material.format != VerificationMaterialFormat.JWK: 41 | raise DIDCommValueError( 42 | f"Unsupported format {secret.verification_material.format}" 43 | ) 44 | res = json.loads(secret.verification_material.value) 45 | res["kid"] = secret.kid 46 | return res 47 | 48 | 49 | def generate_ed25519_keys_as_jwk_dict() -> Tuple[dict, dict]: 50 | """ 51 | Generates ED25519 private and public keys as JWK dicts. 52 | :return: private and public keys as JWK dicts 53 | """ 54 | key = OKPKey.generate_key("Ed25519", is_private=True) 55 | private_key_jwk_dict = key.as_dict(is_private=True) 56 | public_key_jwk_dict = key.as_dict() 57 | return private_key_jwk_dict, public_key_jwk_dict 58 | 59 | 60 | def generate_x25519_keys_as_jwk_dict() -> Tuple[dict, dict]: 61 | """ 62 | Generates X25519 private and public keys as JWK dicts. 63 | :return: private and public keys as JWK dicts 64 | """ 65 | key = OKPKey.generate_key("X25519", is_private=True) 66 | private_key_jwk_dict = key.as_dict(is_private=True) 67 | public_key_jwk_dict = key.as_dict() 68 | return private_key_jwk_dict, public_key_jwk_dict 69 | -------------------------------------------------------------------------------- /docs/release.md: -------------------------------------------------------------------------------- 1 | ## Release 2 | 3 | Assumptions: 4 | 5 | * `main` branch can wait until release PR is merged 6 | 7 | The steps: 8 | 9 | 1. **release**: 10 | 1. **review adjust if needed the release version in `main`** to match the changes from the latest release following the [SemVer rules](https://semver.org/#summary). 11 | 2. [create](https://github.com/sicpa-dlab/didcomm-python/compare/stable...main) a **PR from `main` to `stable`** (you may likely want to name it as `release-`) 12 | 3. once merged [release pipeline](https://github.com/sicpa-dlab/didcomm-python/actions/workflows/release.yml) will publish the release to [PyPI](https://pypi.org/project/didcomm) 13 | 2. **bump next release version in `main`** 14 | * **Note** decision about the next release version should be based on the same [SemVer](https://semver.org/) rules and the expected changes. Usually it would be either a MINOR or MAJOR (if incompatible changes are planned) release. 15 | -------------------------------------------------------------------------------- /docs/testing.md: -------------------------------------------------------------------------------- 1 | # didcomm-python Testing 2 | 3 | ## Table of Contents 4 | 5 | * [Development Environment Setup](#development-environment-setup) 6 | 7 | * [Static Testing](#static-testing) 8 | 9 | * [flake8](#flake8) 10 | * [black](#black) 11 | 12 | * [Unit Testing](#unit-testing) 13 | 14 | ## Development Environment Setup 15 | 16 | ```bash 17 | poetry install 18 | ``` 19 | 20 | ## Static Testing 21 | 22 | ### flake8 23 | 24 | Run [flake8](https://flake8.pycqa.org/en/latest/) as follows: 25 | 26 | ```bash 27 | $ poetry run flake8 . 28 | ``` 29 | 30 | ### black 31 | 32 | Run [black](https://black.readthedocs.io/en/stable/usage_and_configuration/index.html) for a dry check as follows: 33 | 34 | ```bash 35 | $ poetry run black --check . 36 | ``` 37 | 38 | or to auto-format: 39 | 40 | ```bash 41 | $ poetry run black . 42 | ``` 43 | 44 | ## Unit Testing 45 | 46 | To run [pytest](https://docs.pytest.org/en/stable/) in your environment: 47 | 48 | ```bash 49 | poetry run pytest 50 | ``` -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "didcomm" 3 | version = "0.3.3" 4 | description = "Basic DIDComm v2 support in python" 5 | authors = ["SICPA ", "Daniel Bluhm "] 6 | license = "Apache-2.0" 7 | readme = "README.md" 8 | homepage = "https://github.com/sicpa-dlab/didcomm-python" 9 | repository = "https://github.com/sicpa-dlab/didcomm-python" 10 | classifiers = [ 11 | "License :: OSI Approved :: Apache Software License", 12 | "Operating System :: OS Independent", 13 | "Programming Language :: Python :: 3", 14 | "Programming Language :: Python :: 3.7", 15 | ] 16 | include = [ 17 | "LICENSE", 18 | "README.md" 19 | ] 20 | 21 | [tool.poetry.dependencies] 22 | python = "^3.7" 23 | Authlib = "^1.1.0" 24 | pycryptodomex = "~=3.10" 25 | base58 = "~=2.1" 26 | varint = "~=1.0.2" 27 | attrs = "~=22.2" 28 | packaging = "~=23.0" 29 | pydid = "~=0.3.7" 30 | 31 | [tool.poetry.dev-dependencies] 32 | pytest = "^7.1.3" 33 | pytest-asyncio = "^0.20.0" 34 | pytest-xdist = "^3.2.0" 35 | flake8 = "^5.0.4" 36 | black = "^23.1.0" 37 | pytest-mock = "^3.10.0" 38 | mock = "^5.0.1" 39 | 40 | [build-system] 41 | requires = ["poetry-core>=1.0.0"] 42 | build-backend = "poetry.core.masonry.api" 43 | 44 | [tool.pytest.ini_options] 45 | mock_use_standalone_module = true 46 | addopts = "-n auto" 47 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | import mock 3 | 4 | mock_module = mock 5 | except ImportError: 6 | import unittest.mock 7 | 8 | mock_module = unittest.mock 9 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.common.resolvers import ResolversConfig 4 | from tests.test_vectors.did_doc.mock_did_resolver import ( 5 | MockDIDResolverAllInSecrets, 6 | MockDIDResolverWithNonSecrets, 7 | ) 8 | from tests.test_vectors.secrets import ( 9 | MockSecretsResolverAlice, 10 | MockSecretsResolverBob, 11 | MockSecretsResolverCharlie, 12 | MockSecretsResolverMediator1, 13 | MockSecretsResolverMediator2, 14 | ) 15 | from tests.test_vectors.secrets.mock_secrets_resolver_charlie_rotated_to_alice import ( 16 | MockSecretsResolverCharlieRotatedToAlice, 17 | ) 18 | 19 | 20 | @pytest.fixture() 21 | def did_resolver_all_in_secrets(): 22 | return MockDIDResolverAllInSecrets() 23 | 24 | 25 | @pytest.fixture() 26 | def did_resolver_with_non_secrets(): 27 | return MockDIDResolverWithNonSecrets() 28 | 29 | 30 | @pytest.fixture() 31 | def secrets_resolver_alice(): 32 | return MockSecretsResolverAlice() 33 | 34 | 35 | @pytest.fixture() 36 | def secrets_resolver_bob(): 37 | return MockSecretsResolverBob() 38 | 39 | 40 | @pytest.fixture() 41 | def secrets_resolver_charlie(): 42 | return MockSecretsResolverCharlie() 43 | 44 | 45 | @pytest.fixture() 46 | def secrets_resolver_charlie_rotated_to_alice(): 47 | return MockSecretsResolverCharlieRotatedToAlice() 48 | 49 | 50 | @pytest.fixture() 51 | def secrets_resolver_mediator1(): 52 | return MockSecretsResolverMediator1() 53 | 54 | 55 | @pytest.fixture() 56 | def secrets_resolver_mediator2(): 57 | return MockSecretsResolverMediator2() 58 | 59 | 60 | @pytest.fixture() 61 | def resolvers_config_alice_all_in_secrets( 62 | secrets_resolver_alice, did_resolver_all_in_secrets 63 | ): 64 | return ResolversConfig( 65 | secrets_resolver=secrets_resolver_alice, 66 | did_resolver=did_resolver_all_in_secrets, 67 | ) 68 | 69 | 70 | @pytest.fixture() 71 | def resolvers_config_bob_all_in_secrets( 72 | secrets_resolver_bob, did_resolver_all_in_secrets 73 | ): 74 | return ResolversConfig( 75 | secrets_resolver=secrets_resolver_bob, did_resolver=did_resolver_all_in_secrets 76 | ) 77 | 78 | 79 | @pytest.fixture() 80 | def resolvers_config_alice_with_non_secrets( 81 | secrets_resolver_alice, did_resolver_with_non_secrets 82 | ): 83 | return ResolversConfig( 84 | secrets_resolver=secrets_resolver_alice, 85 | did_resolver=did_resolver_with_non_secrets, 86 | ) 87 | 88 | 89 | @pytest.fixture() 90 | def resolvers_config_bob_with_non_secrets( 91 | secrets_resolver_bob, did_resolver_with_non_secrets 92 | ): 93 | return ResolversConfig( 94 | secrets_resolver=secrets_resolver_bob, 95 | did_resolver=did_resolver_with_non_secrets, 96 | ) 97 | 98 | 99 | @pytest.fixture() 100 | def resolvers_config_charlie(secrets_resolver_charlie, did_resolver): 101 | return ResolversConfig( 102 | secrets_resolver=secrets_resolver_charlie, did_resolver=did_resolver 103 | ) 104 | 105 | 106 | @pytest.fixture() 107 | def resolvers_config_charlie_rotated_to_alice( 108 | secrets_resolver_charlie_rotated_to_alice, did_resolver_with_non_secrets 109 | ): 110 | return ResolversConfig( 111 | secrets_resolver=secrets_resolver_charlie_rotated_to_alice, 112 | did_resolver=did_resolver_with_non_secrets, 113 | ) 114 | 115 | 116 | @pytest.fixture() 117 | def resolvers_config_mediator1(secrets_resolver_mediator1, did_resolver): 118 | return ResolversConfig( 119 | secrets_resolver=secrets_resolver_mediator1, did_resolver=did_resolver 120 | ) 121 | 122 | 123 | @pytest.fixture() 124 | def resolvers_config_mediator2(secrets_resolver_mediator2, did_resolver): 125 | return ResolversConfig( 126 | secrets_resolver=secrets_resolver_mediator2, did_resolver=did_resolver 127 | ) 128 | -------------------------------------------------------------------------------- /tests/demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/demo/__init__.py -------------------------------------------------------------------------------- /tests/demo/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture() 5 | def resolvers_config_alice(resolvers_config_alice_with_non_secrets): 6 | return resolvers_config_alice_with_non_secrets 7 | 8 | 9 | @pytest.fixture() 10 | def resolvers_config_bob(resolvers_config_bob_with_non_secrets): 11 | return resolvers_config_bob_with_non_secrets 12 | 13 | 14 | @pytest.fixture() 15 | def did_resolver(did_resolver_with_non_secrets): 16 | return did_resolver_with_non_secrets 17 | -------------------------------------------------------------------------------- /tests/demo/test_demo_advanced_params.py: -------------------------------------------------------------------------------- 1 | import pytest as pytest 2 | 3 | from didcomm import ( 4 | AnonCryptAlg, 5 | Message, 6 | PackEncryptedConfig, 7 | PackEncryptedParameters, 8 | pack_encrypted, 9 | unpack_forward, 10 | unpack, 11 | UnpackConfig, 12 | ) 13 | from tests.test_vectors.common import ALICE_DID, BOB_DID 14 | 15 | 16 | @pytest.mark.asyncio 17 | async def test_demo_advanced_parameters( 18 | resolvers_config_alice, resolvers_config_bob, resolvers_config_mediator1 19 | ): 20 | # ALICE 21 | pack_config = PackEncryptedConfig( 22 | protect_sender_id=True, 23 | forward=True, 24 | enc_alg_anon=AnonCryptAlg.A256GCM_ECDH_ES_A256KW, 25 | ) 26 | # TODO replace hard-coded values 27 | pack_parameters = PackEncryptedParameters( 28 | forward_headers={"expires_time": 99999}, 29 | forward_service_id="did:example:123456789abcdefghi#didcomm-1", 30 | ) 31 | message = Message( 32 | body={"aaa": 1, "bbb": 2}, 33 | id="1234567890", 34 | type="my-protocol/1.0", 35 | frm=ALICE_DID, 36 | to=[BOB_DID], 37 | created_time=1516269022, 38 | expires_time=1516385931, 39 | ) 40 | pack_result = await pack_encrypted( 41 | resolvers_config=resolvers_config_alice, 42 | message=message, 43 | frm="did:example:alice#key-p256-1", 44 | sign_frm="did:example:alice#key-2", 45 | to="did:example:bob#key-p256-1", 46 | pack_config=pack_config, 47 | pack_params=pack_parameters, 48 | ) 49 | packed_msg = pack_result.packed_msg 50 | print(f"Sending {packed_msg} to {pack_result.service_metadata.service_endpoint}") 51 | 52 | # BOB MEDIATOR 53 | forward_bob = await unpack_forward(resolvers_config_mediator1, packed_msg, True) 54 | packed_msg = forward_bob.forwarded_msg 55 | print(f"Sending {packed_msg} to Bob") 56 | 57 | # BOB 58 | unpack_config = UnpackConfig( 59 | expect_decrypt_by_all_keys=False, unwrap_re_wrapping_forward=False 60 | ) 61 | unpack_result = await unpack( 62 | resolvers_config=resolvers_config_bob, 63 | packed_msg=packed_msg, 64 | unpack_config=unpack_config, 65 | ) 66 | print( 67 | f"Got ${unpack_result.message} message signed as ${unpack_result.metadata.signed_message}" 68 | ) 69 | -------------------------------------------------------------------------------- /tests/demo/test_demo_attachments.py: -------------------------------------------------------------------------------- 1 | import pytest as pytest 2 | 3 | from didcomm import ( 4 | Attachment, 5 | Message, 6 | AttachmentDataJson, 7 | pack_encrypted, 8 | PackEncryptedConfig, 9 | unpack_forward, 10 | unpack, 11 | ) 12 | from tests.test_vectors.common import ALICE_DID, BOB_DID 13 | 14 | 15 | @pytest.mark.asyncio 16 | async def test_demo_attachments( 17 | resolvers_config_alice, resolvers_config_bob, resolvers_config_mediator1 18 | ): 19 | # ALICE 20 | attachment = Attachment( 21 | id="123", 22 | data=AttachmentDataJson(json={"foo": "bar"}), 23 | description="foo attachment", 24 | media_type="application/didcomm-encrypted+json", 25 | ) 26 | message = Message( 27 | body={"aaa": 1, "bbb": 2}, 28 | id="1234567890", 29 | type="my-protocol/1.0", 30 | frm=ALICE_DID, 31 | to=[BOB_DID], 32 | created_time=1516269022, 33 | expires_time=1516385931, 34 | attachments=[attachment], 35 | ) 36 | pack_result = await pack_encrypted( 37 | resolvers_config=resolvers_config_alice, 38 | message=message, 39 | frm=ALICE_DID, 40 | to=BOB_DID, 41 | pack_config=PackEncryptedConfig(), 42 | ) 43 | packed_msg = pack_result.packed_msg 44 | print(f"Sending ${packed_msg} to ${pack_result.service_metadata.service_endpoint}") 45 | 46 | # BOB's MEDIATOR 47 | forward_bob = await unpack_forward(resolvers_config_mediator1, packed_msg, True) 48 | print(f"Got {forward_bob.forwarded_msg}") 49 | 50 | # BOB 51 | unpack_result = await unpack(resolvers_config_bob, forward_bob.forwarded_msg) 52 | print(f"Got ${unpack_result.message}") 53 | -------------------------------------------------------------------------------- /tests/demo/test_demo_mediators.py: -------------------------------------------------------------------------------- 1 | import pytest as pytest 2 | 3 | from didcomm import ( 4 | Message, 5 | pack_encrypted, 6 | unpack_forward, 7 | wrap_in_forward, 8 | unpack, 9 | UnpackConfig, 10 | ) 11 | from didcomm.core.serialization import dict_to_json 12 | from tests.test_vectors.common import ALICE_DID, BOB_DID 13 | 14 | 15 | @pytest.mark.asyncio 16 | async def test_demo_mediator( 17 | resolvers_config_alice, resolvers_config_bob, resolvers_config_mediator1 18 | ): 19 | # ALICE 20 | message = Message( 21 | body={"aaa": 1, "bbb": 2}, 22 | id="1234567890", 23 | type="my-protocol/1.0", 24 | frm=ALICE_DID, 25 | to=[BOB_DID], 26 | created_time=1516269022, 27 | expires_time=1516385931, 28 | ) 29 | pack_result = await pack_encrypted( 30 | resolvers_config=resolvers_config_alice, 31 | message=message, 32 | frm=ALICE_DID, 33 | to=BOB_DID, 34 | ) 35 | print( 36 | f"Sending ${pack_result.packed_msg} to ${pack_result.service_metadata.service_endpoint}" 37 | ) 38 | 39 | # BOB MEDIATOR 40 | forward_bob = await unpack_forward( 41 | resolvers_config_mediator1, pack_result.packed_msg, True 42 | ) 43 | print(f"Sending ${forward_bob.forwarded_msg} to Bob") 44 | 45 | # BOB 46 | unpack_result_bob = await unpack(resolvers_config_bob, forward_bob.forwarded_msg) 47 | print(f"Got ${unpack_result_bob.message} message") 48 | 49 | 50 | @pytest.mark.asyncio 51 | async def test_demo_mediators_unknown_to_sender( 52 | resolvers_config_alice, 53 | resolvers_config_bob, 54 | resolvers_config_mediator1, 55 | resolvers_config_mediator2, 56 | ): 57 | # ALICE 58 | message = Message( 59 | body={"aaa": 1, "bbb": 2}, 60 | id="1234567890", 61 | type="my-protocol/1.0", 62 | frm=ALICE_DID, 63 | to=[BOB_DID], 64 | created_time=1516269022, 65 | expires_time=1516385931, 66 | ) 67 | pack_result = await pack_encrypted( 68 | resolvers_config=resolvers_config_alice, 69 | message=message, 70 | frm=ALICE_DID, 71 | to=BOB_DID, 72 | ) 73 | print( 74 | f"Sending ${pack_result.packed_msg} to ${pack_result.service_metadata.service_endpoint}" 75 | ) 76 | 77 | # BOB MEDIATOR 1: re-wrap to a new mediator 78 | forward_bob_1 = await unpack_forward( 79 | resolvers_config_mediator1, pack_result.packed_msg, True 80 | ) 81 | 82 | forward_bob_2 = await wrap_in_forward( 83 | resolvers_config=resolvers_config_mediator1, 84 | packed_msg=forward_bob_1.forwarded_msg, 85 | to=forward_bob_1.forward_msg.body.next, 86 | routing_keys=["did:example:mediator2"], 87 | headers={"expires_time": 99999}, 88 | ) 89 | print(f"Sending ${forward_bob_2} to Bob Mediator 2") 90 | 91 | # BOB MEDIATOR 2 92 | forward_bob = await unpack_forward( 93 | resolvers_config_mediator2, dict_to_json(forward_bob_2.msg_encrypted.msg), True 94 | ) 95 | print(f"Sending ${forward_bob.forwarded_msg} to Bob") 96 | 97 | # BOB 98 | unpack_result_bob = await unpack(resolvers_config_bob, forward_bob.forwarded_msg) 99 | print(f"Got ${unpack_result_bob.message} message") 100 | 101 | 102 | @pytest.mark.asyncio 103 | async def test_demo_re_wrap_to_receiver( 104 | resolvers_config_alice, resolvers_config_bob, resolvers_config_mediator1 105 | ): 106 | # ALICE 107 | message = Message( 108 | body={"aaa": 1, "bbb": 2}, 109 | id="1234567890", 110 | type="my-protocol/1.0", 111 | frm=ALICE_DID, 112 | to=[BOB_DID], 113 | created_time=1516269022, 114 | expires_time=1516385931, 115 | ) 116 | pack_result = await pack_encrypted( 117 | resolvers_config=resolvers_config_alice, 118 | message=message, 119 | frm=ALICE_DID, 120 | to=BOB_DID, 121 | ) 122 | print( 123 | f"Sending ${pack_result.packed_msg} to ${pack_result.service_metadata.service_endpoint}" 124 | ) 125 | 126 | # BOB MEDIATOR 1: re-wrap to Bob 127 | old_forward_bob = await unpack_forward( 128 | resolvers_config_mediator1, pack_result.packed_msg, True 129 | ) 130 | new_packed_forward_bob = await wrap_in_forward( 131 | resolvers_config=resolvers_config_mediator1, 132 | packed_msg=old_forward_bob.forwarded_msg, 133 | to=old_forward_bob.forward_msg.body.next, 134 | routing_keys=[old_forward_bob.forward_msg.body.next], 135 | headers={"expires_time": 99999}, 136 | ) 137 | print(f"Sending ${new_packed_forward_bob} to Bob") 138 | 139 | # BOB 140 | unpack_result_bob = await unpack( 141 | resolvers_config_bob, 142 | dict_to_json(new_packed_forward_bob.msg_encrypted.msg), 143 | unpack_config=UnpackConfig(unwrap_re_wrapping_forward=True), 144 | ) 145 | print(f"Got ${unpack_result_bob.message} message") 146 | -------------------------------------------------------------------------------- /tests/demo/test_demo_rotate_keys.py: -------------------------------------------------------------------------------- 1 | import pytest as pytest 2 | 3 | from didcomm import ( 4 | ResolversConfig, 5 | Message, 6 | FromPrior, 7 | pack_encrypted, 8 | PackEncryptedConfig, 9 | PackEncryptedParameters, 10 | unpack_forward, 11 | SecretsResolverInMemory, 12 | unpack, 13 | ) 14 | from tests.test_vectors.common import CHARLIE_DID, BOB_DID, ALICE_DID 15 | from tests.test_vectors.secrets.mock_secrets_resolver_alice import ( 16 | MockSecretsResolverAlice, 17 | ALICE_SECRET_AUTH_KEY_ED25519, 18 | ) 19 | from tests.test_vectors.secrets.mock_secrets_resolver_charlie import ( 20 | MockSecretsResolverCharlie, 21 | ) 22 | 23 | 24 | class MockSecretsResolverAliceNewDid(SecretsResolverInMemory): 25 | def __init__(self): 26 | super().__init__( 27 | secrets=list(MockSecretsResolverAlice()._secrets.values()) 28 | + list(MockSecretsResolverCharlie()._secrets.values()) 29 | ) 30 | 31 | 32 | @pytest.fixture() 33 | def secrets_resolver_alice_with_new_did(): 34 | return MockSecretsResolverAliceNewDid() 35 | 36 | 37 | @pytest.fixture() 38 | def resolvers_config_alice_with_new_did( 39 | secrets_resolver_alice_with_new_did, did_resolver 40 | ): 41 | return ResolversConfig( 42 | secrets_resolver=secrets_resolver_alice_with_new_did, did_resolver=did_resolver 43 | ) 44 | 45 | 46 | @pytest.mark.asyncio 47 | async def test_demo_attachments( 48 | resolvers_config_alice_with_new_did, 49 | resolvers_config_bob, 50 | resolvers_config_mediator1, 51 | ): 52 | # ALICE 53 | frm_prior = FromPrior(iss=ALICE_DID, sub=CHARLIE_DID) 54 | message = Message( 55 | body={"aaa": 1, "bbb": 2}, 56 | id="1234567890", 57 | type="my-protocol/1.0", 58 | frm=CHARLIE_DID, 59 | to=[BOB_DID], 60 | created_time=1516269022, 61 | expires_time=1516385931, 62 | from_prior=frm_prior, 63 | ) 64 | pack_result = await pack_encrypted( 65 | resolvers_config=resolvers_config_alice_with_new_did, 66 | message=message, 67 | frm=CHARLIE_DID, 68 | to=BOB_DID, 69 | pack_config=PackEncryptedConfig(), 70 | pack_params=PackEncryptedParameters( 71 | from_prior_issuer_kid=ALICE_SECRET_AUTH_KEY_ED25519.kid 72 | ), 73 | ) 74 | packed_msg = pack_result.packed_msg 75 | print(f"Sending ${packed_msg} to ${pack_result.service_metadata.service_endpoint}") 76 | 77 | # BOB's MEDIATOR 78 | forward_bob = await unpack_forward(resolvers_config_mediator1, packed_msg, True) 79 | print(f"Got {forward_bob.forwarded_msg}") 80 | 81 | # BOB 82 | unpack_result = await unpack(resolvers_config_bob, forward_bob.forwarded_msg) 83 | print(f"Got ${unpack_result.message}") 84 | -------------------------------------------------------------------------------- /tests/performance/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/performance/__init__.py -------------------------------------------------------------------------------- /tests/performance/test_performance.py: -------------------------------------------------------------------------------- 1 | from time import perf_counter_ns 2 | 3 | import pytest 4 | import pytest_asyncio 5 | 6 | from didcomm import PackEncryptedConfig, pack_encrypted, unpack, UnpackConfig 7 | from tests.test_vectors.common import BOB_DID, ALICE_DID 8 | from tests.test_vectors.didcomm_messages.messages import TEST_MESSAGE 9 | 10 | RECIPIENT_KEYS_COUNT = [1, 2, 3] 11 | SAMPLES_COUNT = 1000 12 | 13 | 14 | async def measure_naive(sampler, count): 15 | start = perf_counter_ns() 16 | for i in range(count): 17 | await sampler() 18 | end = perf_counter_ns() 19 | time_in_ns = end - start 20 | time_in_ms = time_in_ns / 1000000 21 | return time_in_ms 22 | 23 | 24 | def dump_res( 25 | operations, 26 | time_in_ms, 27 | recipient_keys_count, 28 | unpack=False, 29 | authcrypt=False, 30 | signed=False, 31 | ): 32 | avrg = time_in_ms / operations 33 | thrpt = 1 / avrg 34 | 35 | op_type = "unpack" if unpack else "pack" 36 | enc_type = "_auth" if authcrypt else "_anon" 37 | signed_or_not = "_signed" if signed else "" 38 | 39 | op_descr = ( 40 | f"'{op_type}{enc_type}_encrypted{signed_or_not}' [{recipient_keys_count} kIds]:" 41 | ) 42 | 43 | print( 44 | f"\nbenchmark of {op_descr:40}" 45 | f" {time_in_ms:7.3f} ms, {operations:7} ops, {thrpt:10.3f} ops/ms, {avrg:7.3f} mss/op" 46 | ) 47 | 48 | 49 | @pytest.fixture() 50 | def resolvers_config_alice(resolvers_config_alice_all_in_secrets): 51 | return resolvers_config_alice_all_in_secrets 52 | 53 | 54 | @pytest.fixture() 55 | def resolvers_config_bob(resolvers_config_bob_all_in_secrets): 56 | return resolvers_config_bob_all_in_secrets 57 | 58 | 59 | @pytest_asyncio.fixture() 60 | async def all_bob_key_agreement_kids(resolvers_config_alice): 61 | bob_did_doc = await resolvers_config_alice.did_resolver.resolve(BOB_DID) 62 | all_key_agreement_kids = bob_did_doc.key_agreement 63 | yield all_key_agreement_kids 64 | bob_did_doc.key_agreement = all_key_agreement_kids 65 | 66 | 67 | @pytest.mark.skip(reason="disabled to skip on CI") 68 | @pytest.mark.asyncio 69 | @pytest.mark.parametrize("recipient_keys_count", RECIPIENT_KEYS_COUNT) 70 | @pytest.mark.parametrize("sign_frm", [None, ALICE_DID]) 71 | @pytest.mark.parametrize("frm", [None, ALICE_DID]) 72 | async def test_pack_encrypted( 73 | frm, 74 | sign_frm, 75 | recipient_keys_count, 76 | resolvers_config_alice, 77 | all_bob_key_agreement_kids, 78 | ): 79 | bob_did_doc = await resolvers_config_alice.did_resolver.resolve(BOB_DID) 80 | bob_did_doc.key_agreement = all_bob_key_agreement_kids[:recipient_keys_count] 81 | 82 | async def sample(): 83 | pack_config = PackEncryptedConfig() 84 | 85 | await pack_encrypted( 86 | resolvers_config=resolvers_config_alice, 87 | message=TEST_MESSAGE, 88 | to=BOB_DID, 89 | frm=frm, 90 | sign_frm=sign_frm, 91 | pack_config=pack_config, 92 | ) 93 | 94 | time_is_ms = await measure_naive(sample, SAMPLES_COUNT) 95 | dump_res( 96 | SAMPLES_COUNT, 97 | time_is_ms, 98 | recipient_keys_count, 99 | False, 100 | bool(frm), 101 | bool(sign_frm), 102 | ) 103 | 104 | 105 | @pytest.mark.skip(reason="disabled to skip on CI") 106 | @pytest.mark.asyncio 107 | @pytest.mark.parametrize("recipient_keys_count", RECIPIENT_KEYS_COUNT) 108 | @pytest.mark.parametrize("sign_frm", [None, ALICE_DID]) 109 | @pytest.mark.parametrize("frm", [None, ALICE_DID]) 110 | async def test_unpack_encrypted( 111 | frm, 112 | sign_frm, 113 | recipient_keys_count, 114 | resolvers_config_alice, 115 | resolvers_config_bob, 116 | all_bob_key_agreement_kids, 117 | ): 118 | bob_did_doc = await resolvers_config_alice.did_resolver.resolve(BOB_DID) 119 | bob_did_doc.key_agreement = all_bob_key_agreement_kids[:recipient_keys_count] 120 | 121 | pack_config = PackEncryptedConfig() 122 | 123 | pack_result = await pack_encrypted( 124 | resolvers_config=resolvers_config_alice, 125 | message=TEST_MESSAGE, 126 | to=BOB_DID, 127 | frm=frm, 128 | sign_frm=sign_frm, 129 | pack_config=pack_config, 130 | ) 131 | 132 | async def sample(): 133 | unpack_config = UnpackConfig(expect_decrypt_by_all_keys=True) 134 | 135 | await unpack( 136 | resolvers_config=resolvers_config_bob, 137 | packed_msg=pack_result.packed_msg, 138 | unpack_config=unpack_config, 139 | ) 140 | 141 | time_is_ms = await measure_naive(sample, SAMPLES_COUNT) 142 | dump_res( 143 | SAMPLES_COUNT, time_is_ms, recipient_keys_count, True, bool(frm), bool(sign_frm) 144 | ) 145 | -------------------------------------------------------------------------------- /tests/test_vectors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/test_vectors/__init__.py -------------------------------------------------------------------------------- /tests/test_vectors/common.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Any 3 | 4 | from didcomm.common.types import JSON, DID 5 | from didcomm import Metadata 6 | 7 | ALICE_DID = DID("did:example:alice") 8 | BOB_DID = DID("did:example:bob") 9 | CHARLIE_DID = DID("did:example:charlie") 10 | DAVE_DID = DID("did:example:dave") 11 | 12 | 13 | # Note. additional prefix `T` is to hide the clases 14 | # from pytest tests collector 15 | 16 | 17 | @dataclass 18 | class TTestVector: 19 | value: JSON 20 | metadata: Metadata 21 | 22 | 23 | @dataclass 24 | class TTestVectorNegative: 25 | value: JSON 26 | exc: Any 27 | -------------------------------------------------------------------------------- /tests/test_vectors/did_doc/__init__.py: -------------------------------------------------------------------------------- 1 | from .did_doc_alice import ( 2 | DID_DOC_ALICE_WITH_NO_SECRETS, 3 | DID_DOC_ALICE_SPEC_TEST_VECTORS, 4 | ) 5 | from .did_doc_bob import DID_DOC_BOB_SPEC_TEST_VECTORS, DID_DOC_BOB_WITH_NO_SECRETS 6 | from .did_doc_charlie import DID_DOC_CHARLIE 7 | from .did_doc_mediator1 import DID_DOC_MEDIATOR1 8 | from .did_doc_mediator2 import DID_DOC_MEDIATOR2 9 | 10 | 11 | __all__ = [ 12 | "DID_DOC_ALICE_WITH_NO_SECRETS", 13 | "DID_DOC_ALICE_SPEC_TEST_VECTORS", 14 | "DID_DOC_BOB_SPEC_TEST_VECTORS", 15 | "DID_DOC_BOB_WITH_NO_SECRETS", 16 | "DID_DOC_CHARLIE", 17 | "DID_DOC_MEDIATOR1", 18 | "DID_DOC_MEDIATOR2", 19 | ] 20 | -------------------------------------------------------------------------------- /tests/test_vectors/did_doc/did_doc_charlie.py: -------------------------------------------------------------------------------- 1 | from didcomm import DIDDoc, DIDCommService, VerificationMethod, VerificationMethodType 2 | from didcomm.protocols.routing.forward import ( 3 | PROFILE_DIDCOMM_V2, 4 | PROFILE_DIDCOMM_AIP2_ENV_RFC587, 5 | ) 6 | from tests.test_vectors.common import CHARLIE_DID 7 | 8 | CHARLIE_VERIFICATION_METHOD_KEY_AGREEM_X25519 = VerificationMethod( 9 | id="did:example:charlie#key-x25519-1", 10 | controller="did:example:charlie", 11 | type=VerificationMethodType.JSON_WEB_KEY_2020, 12 | public_key_jwk={ 13 | "kty": "OKP", 14 | "crv": "X25519", 15 | "x": "nTiVFj7DChMsETDdxd5dIzLAJbSQ4j4UG6ZU1ogLNlw", 16 | }, 17 | ) 18 | 19 | CHARLIE_AUTH_METHOD_25519 = VerificationMethod( 20 | id="did:example:charlie#key-1", 21 | controller="did:example:charlie", 22 | type=VerificationMethodType.JSON_WEB_KEY_2020, 23 | public_key_jwk={ 24 | "kty": "OKP", 25 | "crv": "Ed25519", 26 | "x": "VDXDwuGKVq91zxU6q7__jLDUq8_C5cuxECgd-1feFTE", 27 | }, 28 | ) 29 | 30 | DID_DOC_CHARLIE = DIDDoc( 31 | id=CHARLIE_DID, 32 | authentication=["did:example:charlie#key-1"], 33 | key_agreement=["did:example:charlie#key-x25519-1"], 34 | service=[ 35 | DIDCommService( 36 | id="did:example:123456789abcdefghi#didcomm-1", 37 | service_endpoint="did:example:mediator2#key-x25519-1", 38 | accept=[PROFILE_DIDCOMM_V2, PROFILE_DIDCOMM_AIP2_ENV_RFC587], 39 | recipient_keys=[], 40 | routing_keys=["did:example:mediator1#key-x25519-1"], 41 | ) 42 | ], 43 | verification_method=[ 44 | CHARLIE_VERIFICATION_METHOD_KEY_AGREEM_X25519, 45 | CHARLIE_AUTH_METHOD_25519, 46 | ], 47 | ) 48 | -------------------------------------------------------------------------------- /tests/test_vectors/did_doc/did_doc_mediator1.py: -------------------------------------------------------------------------------- 1 | from didcomm import DIDDoc, VerificationMethod, VerificationMethodType 2 | 3 | # FIXME build verification material 4 | # (currently it's a copy-paste from Bob's ones) 5 | MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_X25519_1 = VerificationMethod( 6 | id="did:example:mediator1#key-x25519-1", 7 | controller="did:example:mediator1", 8 | type=VerificationMethodType.JSON_WEB_KEY_2020, 9 | public_key_jwk={ 10 | "kty": "OKP", 11 | "crv": "X25519", 12 | "x": "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", 13 | }, 14 | ) 15 | MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P256_1 = VerificationMethod( 16 | id="did:example:mediator1#key-p256-1", 17 | controller="did:example:mediator1", 18 | type=VerificationMethodType.JSON_WEB_KEY_2020, 19 | public_key_jwk={ 20 | "kty": "EC", 21 | "crv": "P-256", 22 | "x": "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", 23 | "y": "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", 24 | }, 25 | ) 26 | MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P384_1 = VerificationMethod( 27 | id="did:example:mediator1#key-p384-1", 28 | controller="did:example:mediator1", 29 | type=VerificationMethodType.JSON_WEB_KEY_2020, 30 | public_key_jwk={ 31 | "kty": "EC", 32 | "crv": "P-384", 33 | "x": "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", 34 | "y": "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", 35 | }, 36 | ) 37 | MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P521_1 = VerificationMethod( 38 | id="did:example:mediator1#key-p521-1", 39 | controller="did:example:mediator1", 40 | type=VerificationMethodType.JSON_WEB_KEY_2020, 41 | public_key_jwk={ 42 | "kty": "EC", 43 | "crv": "P-521", 44 | "x": "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", 45 | "y": "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", 46 | }, 47 | ) 48 | 49 | DID_DOC_MEDIATOR1_SPEC_TEST_VECTORS = DIDDoc( 50 | id="did:example:mediator1", 51 | authentication=[], 52 | key_agreement=[ 53 | "did:example:mediator1#key-x25519-1", 54 | "did:example:mediator1#key-p256-1", 55 | "did:example:mediator1#key-p384-1", 56 | "did:example:mediator1#key-p521-1", 57 | ], 58 | service=[], 59 | verification_method=[ 60 | MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_X25519_1, 61 | MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P256_1, 62 | MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P384_1, 63 | MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P521_1, 64 | ], 65 | ) 66 | 67 | DID_DOC_MEDIATOR1 = DIDDoc( 68 | id="did:example:mediator1", 69 | authentication=[], 70 | key_agreement=[ 71 | "did:example:mediator1#key-x25519-1", 72 | "did:example:mediator1#key-p256-1", 73 | "did:example:mediator1#key-p384-1", 74 | "did:example:mediator1#key-p521-1", 75 | ], 76 | service=[], 77 | verification_method=[ 78 | MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_X25519_1, 79 | MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P256_1, 80 | MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P384_1, 81 | MEDIATOR1_VERIFICATION_METHOD_KEY_AGREEM_P521_1, 82 | ], 83 | ) 84 | -------------------------------------------------------------------------------- /tests/test_vectors/did_doc/did_doc_mediator2.py: -------------------------------------------------------------------------------- 1 | from didcomm import ( 2 | DIDDoc, 3 | DIDCommService, 4 | VerificationMethod, 5 | VerificationMethodType, 6 | ) 7 | 8 | # FIXME build verification material 9 | # (currently it's a copy-paste from Bob's ones) 10 | from didcomm.protocols.routing.forward import ( 11 | PROFILE_DIDCOMM_V2, 12 | PROFILE_DIDCOMM_AIP2_ENV_RFC587, 13 | ) 14 | 15 | MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_X25519_1 = VerificationMethod( 16 | id="did:example:mediator2#key-x25519-1", 17 | controller="did:example:mediator2", 18 | type=VerificationMethodType.JSON_WEB_KEY_2020, 19 | public_key_jwk={ 20 | "kty": "OKP", 21 | "crv": "X25519", 22 | "x": "UT9S3F5ep16KSNBBShU2wh3qSfqYjlasZimn0mB8_VM", 23 | }, 24 | ) 25 | MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P256_1 = VerificationMethod( 26 | id="did:example:mediator2#key-p256-1", 27 | controller="did:example:mediator2", 28 | type=VerificationMethodType.JSON_WEB_KEY_2020, 29 | public_key_jwk={ 30 | "kty": "EC", 31 | "crv": "P-256", 32 | "x": "n0yBsGrwGZup9ywKhzD4KoORGicilzIUyfcXb1CSwe0", 33 | "y": "ov0buZJ8GHzV128jmCw1CaFbajZoFFmiJDbMrceCXIw", 34 | }, 35 | ) 36 | MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P384_1 = VerificationMethod( 37 | id="did:example:mediator2#key-p384-1", 38 | controller="did:example:mediator2", 39 | type=VerificationMethodType.JSON_WEB_KEY_2020, 40 | public_key_jwk={ 41 | "kty": "EC", 42 | "crv": "P-384", 43 | "x": "2x3HOTvR8e-Tu6U4UqMd1wUWsNXMD0RgIunZTMcZsS-zWOwDgsrhYVHmv3k_DjV3", 44 | "y": "W9LLaBjlWYcXUxOf6ECSfcXKaC3-K9z4hCoP0PS87Q_4ExMgIwxVCXUEB6nf0GDd", 45 | }, 46 | ) 47 | MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P521_1 = VerificationMethod( 48 | id="did:example:mediator2#key-p521-1", 49 | controller="did:example:mediator2", 50 | type=VerificationMethodType.JSON_WEB_KEY_2020, 51 | public_key_jwk={ 52 | "kty": "EC", 53 | "crv": "P-521", 54 | "x": "ATp_WxCfIK_SriBoStmA0QrJc2pUR1djpen0VdpmogtnKxJbitiPq-HJXYXDKriXfVnkrl2i952MsIOMfD2j0Ots", 55 | "y": "AEJipR0Dc-aBZYDqN51SKHYSWs9hM58SmRY1MxgXANgZrPaq1EeGMGOjkbLMEJtBThdjXhkS5VlXMkF0cYhZELiH", 56 | }, 57 | ) 58 | 59 | DID_DOC_MEDIATOR2_SPEC_TEST_VECTORS = DIDDoc( 60 | id="did:example:mediator2", 61 | authentication=[], 62 | key_agreement=[ 63 | "did:example:mediator2#key-x25519-1", 64 | "did:example:mediator2#key-p256-1", 65 | "did:example:mediator2#key-p384-1", 66 | "did:example:mediator2#key-p521-1", 67 | ], 68 | service=[], 69 | verification_method=[ 70 | MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_X25519_1, 71 | MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P256_1, 72 | MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P384_1, 73 | MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P521_1, 74 | ], 75 | ) 76 | 77 | DID_DOC_MEDIATOR2 = DIDDoc( 78 | id="did:example:mediator2", 79 | authentication=[], 80 | key_agreement=[ 81 | "did:example:mediator2#key-x25519-1", 82 | "did:example:mediator2#key-p256-1", 83 | "did:example:mediator2#key-p384-1", 84 | "did:example:mediator2#key-p521-1", 85 | ], 86 | service=[ 87 | DIDCommService( 88 | id="did:example:123456789abcdefghi#didcomm-1", 89 | service_endpoint="http://example.com/path", 90 | accept=[PROFILE_DIDCOMM_V2, PROFILE_DIDCOMM_AIP2_ENV_RFC587], 91 | routing_keys=["did:example:mediator1#key-x25519-1"], 92 | recipient_keys=[], 93 | ) 94 | ], 95 | verification_method=[ 96 | MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_X25519_1, 97 | MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P256_1, 98 | MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P384_1, 99 | MEDIATOR2_VERIFICATION_METHOD_KEY_AGREEM_P521_1, 100 | ], 101 | ) 102 | -------------------------------------------------------------------------------- /tests/test_vectors/did_doc/mock_did_resolver.py: -------------------------------------------------------------------------------- 1 | from didcomm import DIDResolverInMemory 2 | from tests.test_vectors.did_doc import ( 3 | DID_DOC_ALICE_WITH_NO_SECRETS, 4 | DID_DOC_ALICE_SPEC_TEST_VECTORS, 5 | DID_DOC_BOB_WITH_NO_SECRETS, 6 | DID_DOC_BOB_SPEC_TEST_VECTORS, 7 | DID_DOC_CHARLIE, 8 | DID_DOC_MEDIATOR1, 9 | DID_DOC_MEDIATOR2, 10 | ) 11 | 12 | 13 | class MockDIDResolverAllInSecrets(DIDResolverInMemory): 14 | def __init__(self): 15 | super().__init__( 16 | did_docs=[ 17 | DID_DOC_ALICE_SPEC_TEST_VECTORS, 18 | DID_DOC_BOB_SPEC_TEST_VECTORS, 19 | DID_DOC_CHARLIE, 20 | DID_DOC_MEDIATOR1, 21 | DID_DOC_MEDIATOR2, 22 | ] 23 | ) 24 | 25 | 26 | class MockDIDResolverWithNonSecrets(DIDResolverInMemory): 27 | def __init__(self): 28 | super().__init__( 29 | did_docs=[ 30 | DID_DOC_ALICE_WITH_NO_SECRETS, 31 | DID_DOC_BOB_WITH_NO_SECRETS, 32 | DID_DOC_CHARLIE, 33 | DID_DOC_MEDIATOR1, 34 | DID_DOC_MEDIATOR2, 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /tests/test_vectors/didcomm_messages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/test_vectors/didcomm_messages/__init__.py -------------------------------------------------------------------------------- /tests/test_vectors/didcomm_messages/spec/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/test_vectors/didcomm_messages/spec/__init__.py -------------------------------------------------------------------------------- /tests/test_vectors/didcomm_messages/spec/spec_test_vectors_plaintext.py: -------------------------------------------------------------------------------- 1 | from authlib.common.encoding import json_dumps 2 | 3 | from didcomm import Metadata 4 | 5 | PLAINTEXT_EXPECTED_METADATA = Metadata( 6 | encrypted=False, 7 | authenticated=False, 8 | non_repudiation=False, 9 | anonymous_sender=False, 10 | ) 11 | 12 | TEST_PLAINTEXT_DIDCOMM_MESSAGE_SIMPLE = json_dumps( 13 | { 14 | "id": "1234567890", 15 | "thid": "1234567890", 16 | "typ": "application/didcomm-plain+json", 17 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 18 | "from": "did:example:alice", 19 | "to": ["did:example:bob"], 20 | "created_time": 1516269022, 21 | "expires_time": 1516385931, 22 | "body": {"messagespecificattribute": "and its value"}, 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /tests/test_vectors/didcomm_messages/spec/spec_test_vectors_signed.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from authlib.common.encoding import json_dumps 4 | 5 | from didcomm import SignAlg, Metadata 6 | from tests.test_vectors.common import TTestVector 7 | 8 | TEST_SIGNED_DIDCOMM_MESSAGE_ALICE_KEY_1 = json_dumps( 9 | { 10 | "payload": "eyJ0eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3NhbCIsImJvZHkiOnsibWVzc2FnZXNwZWNpZmljYXR0cmlidXRlIjoiYW5kIGl0cyB2YWx1ZSJ9LCJpZCI6IjEyMzQ1Njc4OTAiLCJ0byI6WyJkaWQ6ZXhhbXBsZTpib2IiXSwiY3JlYXRlZF90aW1lIjoxNTE2MjY5MDIyLCJleHBpcmVzX3RpbWUiOjE1MTYzODU5MzEsInRoaWQiOiIxMjM0NTY3ODkwIiwiZnJvbSI6ImRpZDpleGFtcGxlOmFsaWNlIiwidHlwIjoiYXBwbGljYXRpb24vZGlkY29tbS1wbGFpbitqc29uIn0", 11 | "signatures": [ 12 | { 13 | "protected": "eyJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXNpZ25lZCtqc29uIiwiYWxnIjoiRWREU0EifQ", 14 | "signature": "D3aEM0sIPobl-qnJh7kF1Hol6Wz_CKmyqHfgwGbFKmjyWvUoXhCI09ZiE5qmJlyyPo_ubqEqWOiPYcroEjJ9Ag", 15 | "header": {"kid": "did:example:alice#key-1"}, 16 | } 17 | ], 18 | } 19 | ) 20 | 21 | TEST_SIGNED_DIDCOMM_MESSAGE_ALICE_KEY_2 = json_dumps( 22 | { 23 | "payload": "eyJ0eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3NhbCIsImJvZHkiOnsibWVzc2FnZXNwZWNpZmljYXR0cmlidXRlIjoiYW5kIGl0cyB2YWx1ZSJ9LCJpZCI6IjEyMzQ1Njc4OTAiLCJ0byI6WyJkaWQ6ZXhhbXBsZTpib2IiXSwiY3JlYXRlZF90aW1lIjoxNTE2MjY5MDIyLCJleHBpcmVzX3RpbWUiOjE1MTYzODU5MzEsInRoaWQiOiIxMjM0NTY3ODkwIiwiZnJvbSI6ImRpZDpleGFtcGxlOmFsaWNlIiwidHlwIjoiYXBwbGljYXRpb24vZGlkY29tbS1wbGFpbitqc29uIn0", 24 | "signatures": [ 25 | { 26 | "protected": "eyJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXNpZ25lZCtqc29uIiwiYWxnIjoiRVMyNTYifQ", 27 | "signature": "YktGGyR1i23DGdMz0_yhrb5o4zLKja3VT-qKMFD2VhIX2Vb12SkbD2kREc6HZE-NcIBXmpTGu1P-HtWRp-Ys0g", 28 | "header": {"kid": "did:example:alice#key-2"}, 29 | } 30 | ], 31 | } 32 | ) 33 | 34 | TEST_SIGNED_DIDCOMM_MESSAGE_ALICE_KEY_3 = json_dumps( 35 | { 36 | "payload": "eyJ0eXBlIjoiaHR0cDovL2V4YW1wbGUuY29tL3Byb3RvY29scy9sZXRzX2RvX2x1bmNoLzEuMC9wcm9wb3NhbCIsImJvZHkiOnsibWVzc2FnZXNwZWNpZmljYXR0cmlidXRlIjoiYW5kIGl0cyB2YWx1ZSJ9LCJpZCI6IjEyMzQ1Njc4OTAiLCJ0byI6WyJkaWQ6ZXhhbXBsZTpib2IiXSwiY3JlYXRlZF90aW1lIjoxNTE2MjY5MDIyLCJleHBpcmVzX3RpbWUiOjE1MTYzODU5MzEsInRoaWQiOiIxMjM0NTY3ODkwIiwiZnJvbSI6ImRpZDpleGFtcGxlOmFsaWNlIiwidHlwIjoiYXBwbGljYXRpb24vZGlkY29tbS1wbGFpbitqc29uIn0", 37 | "signatures": [ 38 | { 39 | "protected": "eyJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLXNpZ25lZCtqc29uIiwiYWxnIjoiRVMyNTZLIn0", 40 | "signature": "mYLZAUz97lodaWbwED6IvBf9_8Zj41-dM6r_3gTE9AkVZI3AJlz-agDBNSmNHd3CjRKW1KNS10pND57123b9aA", 41 | "header": {"kid": "did:example:alice#key-3"}, 42 | } 43 | ], 44 | } 45 | ) 46 | 47 | TEST_SIGNED_DIDCOMM_MESSAGE = [ 48 | TTestVector( 49 | TEST_SIGNED_DIDCOMM_MESSAGE_ALICE_KEY_1, 50 | Metadata( 51 | encrypted=False, 52 | non_repudiation=True, 53 | authenticated=True, 54 | anonymous_sender=False, 55 | sign_from="did:example:alice#key-1", 56 | sign_alg=SignAlg.ED25519, 57 | signed_message=copy.deepcopy(TEST_SIGNED_DIDCOMM_MESSAGE_ALICE_KEY_1), 58 | ), 59 | ), 60 | TTestVector( 61 | TEST_SIGNED_DIDCOMM_MESSAGE_ALICE_KEY_2, 62 | Metadata( 63 | encrypted=False, 64 | non_repudiation=True, 65 | authenticated=True, 66 | anonymous_sender=False, 67 | sign_from="did:example:alice#key-2", 68 | sign_alg=SignAlg.ES256, 69 | signed_message=TEST_SIGNED_DIDCOMM_MESSAGE_ALICE_KEY_2, 70 | ), 71 | ), 72 | TTestVector( 73 | TEST_SIGNED_DIDCOMM_MESSAGE_ALICE_KEY_3, 74 | Metadata( 75 | encrypted=False, 76 | non_repudiation=True, 77 | authenticated=True, 78 | anonymous_sender=False, 79 | sign_from="did:example:alice#key-3", 80 | sign_alg=SignAlg.ES256K, 81 | signed_message=TEST_SIGNED_DIDCOMM_MESSAGE_ALICE_KEY_3, 82 | ), 83 | ), 84 | ] 85 | -------------------------------------------------------------------------------- /tests/test_vectors/didcomm_messages/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/test_vectors/didcomm_messages/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_vectors/didcomm_messages/tests/common.py: -------------------------------------------------------------------------------- 1 | from authlib.common.encoding import ( 2 | json_loads, 3 | json_dumps, 4 | to_unicode, 5 | to_bytes, 6 | urlsafe_b64decode, 7 | urlsafe_b64encode, 8 | ) 9 | 10 | 11 | def update(msg, field, new_value): 12 | msg = json_loads(msg) 13 | msg[field] = new_value 14 | return json_dumps(msg) 15 | 16 | 17 | def update_protected(msg, field, new_value): 18 | msg = json_loads(msg) 19 | # TODO: Use an util conversion function 20 | protected = json_loads(to_unicode(urlsafe_b64decode(to_bytes(msg["protected"])))) 21 | protected[field] = new_value 22 | # TODO: Use an util conversion function 23 | msg["protected"] = to_unicode(urlsafe_b64encode(to_bytes(json_dumps(protected)))) 24 | return json_dumps(msg) 25 | -------------------------------------------------------------------------------- /tests/test_vectors/didcomm_messages/tests/test_vectors_anoncrypt_negative.py: -------------------------------------------------------------------------------- 1 | from authlib.common.encoding import json_dumps 2 | 3 | from didcomm.errors import MalformedMessageError 4 | from tests.test_vectors.common import TTestVectorNegative 5 | from tests.test_vectors.didcomm_messages.spec.spec_test_vectors_anon_encrypted import ( 6 | TEST_ENCRYPTED_DIDCOMM_MESSAGE_ANON_XC20P_1, 7 | ) 8 | from tests.test_vectors.didcomm_messages.tests.common import update, update_protected 9 | 10 | INVALID_MESSAGES = [ 11 | update(TEST_ENCRYPTED_DIDCOMM_MESSAGE_ANON_XC20P_1, "protected", "invalid"), 12 | update_protected(TEST_ENCRYPTED_DIDCOMM_MESSAGE_ANON_XC20P_1, "apv", "invalid"), 13 | update(TEST_ENCRYPTED_DIDCOMM_MESSAGE_ANON_XC20P_1, "iv", "invalid"), 14 | update(TEST_ENCRYPTED_DIDCOMM_MESSAGE_ANON_XC20P_1, "ciphertext", "invalid"), 15 | update(TEST_ENCRYPTED_DIDCOMM_MESSAGE_ANON_XC20P_1, "tag", "invalid"), 16 | # TODO: add more 17 | ] 18 | 19 | INVALID_ANONCRYPT_TEST_VECTORS = [ 20 | TTestVectorNegative(value, MalformedMessageError) for value in INVALID_MESSAGES 21 | ] 22 | 23 | ANONCRYPT_MESSAGE_P256_XC20P_EPK_WRONG_POINT = json_dumps( 24 | { 25 | "protected": "eyJ0eXAiOiJhcHBsaWNhdGlvbi9kaWRjb21tLWVuY3J5cHRlZCtqc29uIiwiYWxnIjoiRUNESC1FUytBMjU2S1ciLCJlbmMiOiJYQzIwUCIsImFwdSI6bnVsbCwiYXB2Ijoiei1McXB2VlhEYl9zR1luM21qUUxwdXUyQ1FMZXdZdVpvVFdPSVhQSDNGTSIsImVwayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IkZSQW1UQmljUFZJXy1aRnF2WEJwNzZhV2pZM0gzYlpGZlhocHRUNm1ETnciLCJ5IjoiLXZ0LTFIaHRvVjBwN2xrbGIxTnRvMWRhU0lqQnV3cVZzbGIwcC1uOWRrdyJ9fQ==", 26 | "recipients": [ 27 | { 28 | "header": {"kid": "did:example:bob#key-p256-1"}, 29 | "encrypted_key": "scQxV9YQ4mQrUHgl6yAnBFDXNZAiIs_15bmoErUmoYm0HtuRclPoQg", 30 | }, 31 | { 32 | "header": {"kid": "did:example:bob#key-p256-2"}, 33 | "encrypted_key": "CqZ-HDH2j0NC-eoUueNLKyAuMQXjQyw8bJHYM2f-lxJVm3eXCdmm2g", 34 | }, 35 | ], 36 | "iv": "Vg1uyuQKrU6Kw8OJK38WCpYFxW0suAP9", 37 | "ciphertext": "2nIm3xQcFR3HXbUPF1HS_D92OGVDvL0nIi6O5ol5tnMIa09NxJtbVAYIG7ZrkT9314PqXn_Rq77hgGE6FAOgO7aNYLyUJh0JCC_i2p_XOWuk20BYyBsmmRvVpg0DY3I1Lb-Vg1pT9pEy09gsMSLhbfqk0_TFJB1rcqzR8W0YZB5mX_53nMRf1ZatDEg4rDogSekWEGTBnlTNRua8-zoI4573SfgJ-ONt7Z_KbGO-sdRkmqXhfYNcbUyoMF9JSa-kraVuWHZP9hTz8-7R020EXfb4jodMWVOMMAiJYk1Cd7tetHXpLPdtuokaapofmtL_SNftAX2CB6ULf0axrHUNtvUyjAPvpgvSuvQuMrDlaXn16MQJ_q55", 38 | "tag": "etLTQvKsTvF629fykLiUDg", 39 | } 40 | ) 41 | -------------------------------------------------------------------------------- /tests/test_vectors/didcomm_messages/tests/test_vectors_authcrypt_negative.py: -------------------------------------------------------------------------------- 1 | from didcomm.errors import MalformedMessageError 2 | from tests.test_vectors.common import TTestVectorNegative 3 | from tests.test_vectors.didcomm_messages.spec.spec_test_vectors_auth_encrypted import ( 4 | TEST_ENCRYPTED_DIDCOMM_MESSAGE_AUTH_X25519, 5 | ) 6 | from tests.test_vectors.didcomm_messages.tests.common import update, update_protected 7 | 8 | INVALID_MESSAGES = [ 9 | update(TEST_ENCRYPTED_DIDCOMM_MESSAGE_AUTH_X25519, "protected", "invalid"), 10 | update_protected(TEST_ENCRYPTED_DIDCOMM_MESSAGE_AUTH_X25519, "apv", "invalid"), 11 | update(TEST_ENCRYPTED_DIDCOMM_MESSAGE_AUTH_X25519, "iv", "invalid"), 12 | update(TEST_ENCRYPTED_DIDCOMM_MESSAGE_AUTH_X25519, "ciphertext", "invalid"), 13 | update(TEST_ENCRYPTED_DIDCOMM_MESSAGE_AUTH_X25519, "tag", "invalid"), 14 | # TODO: add more 15 | ] 16 | 17 | INVALID_AUTHCRYPT_TEST_VECTORS = [ 18 | TTestVectorNegative(value, MalformedMessageError) for value in INVALID_MESSAGES 19 | ] 20 | -------------------------------------------------------------------------------- /tests/test_vectors/didcomm_messages/tests/test_vectors_plaintext_negative.py: -------------------------------------------------------------------------------- 1 | from authlib.common.encoding import json_dumps 2 | 3 | from didcomm.errors import MalformedMessageError 4 | from tests.test_vectors.common import TTestVectorNegative 5 | 6 | INVALID_MESSAGES = [ 7 | json_dumps({}), 8 | json_dumps("aaa"), 9 | json_dumps( 10 | { 11 | "typ": "application/didcomm-plain+json", 12 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 13 | "body": {}, 14 | } 15 | ), 16 | json_dumps( 17 | { 18 | "id": "1234567890", 19 | "typ": "application/didcomm-plain+json", 20 | "body": {}, 21 | } 22 | ), 23 | json_dumps( 24 | { 25 | "id": "1234567890", 26 | "typ": "application/didcomm-plain+json", 27 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 28 | } 29 | ), 30 | json_dumps( 31 | { 32 | "id": "1234567890", 33 | "typ": "application/didcomm-plain+json-unknown", 34 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 35 | "body": {}, 36 | } 37 | ), 38 | json_dumps( 39 | { 40 | "id": "1234567890", 41 | "typ": "application/didcomm-plain+json", 42 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 43 | "body": {}, 44 | "attachments": [{}], 45 | } 46 | ), 47 | json_dumps( 48 | { 49 | "id": "1234567890", 50 | "typ": "application/didcomm-plain+json", 51 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 52 | "body": {}, 53 | "attachments": [{"id": "23"}], 54 | } 55 | ), 56 | json_dumps( 57 | { 58 | "id": "1234567890", 59 | "typ": "application/didcomm-plain+json", 60 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 61 | "body": {}, 62 | "attachments": [{"id": "23", "data": {}}], 63 | } 64 | ), 65 | json_dumps( 66 | { 67 | "id": "1234567890", 68 | "typ": "application/didcomm-plain+json", 69 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 70 | "body": {}, 71 | "attachments": [{"id": "23", "data": {"links": ["231", "212"]}}], 72 | } 73 | ), 74 | json_dumps( 75 | { 76 | "id": "1234567890", 77 | "typ": "application/didcomm-plain+json", 78 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 79 | "body": {}, 80 | "attachments": "131", 81 | } 82 | ), 83 | json_dumps( 84 | { 85 | "id": "1234567890", 86 | "typ": "application/didcomm-plain+json", 87 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 88 | "body": {}, 89 | "attachments": [2131], 90 | } 91 | ), 92 | json_dumps( 93 | { 94 | "id": "1234567890", 95 | "typ": "application/didcomm-plain+json", 96 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 97 | "body": {}, 98 | "attachments": [{"id": 2}], 99 | } 100 | ), 101 | json_dumps( 102 | { 103 | "id": "1234567890", 104 | "typ": "application/didcomm-plain+json", 105 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 106 | "body": {}, 107 | "attachments": [{"id": "1", "data": None}], 108 | } 109 | ), 110 | json_dumps( 111 | { 112 | "id": "1234567890", 113 | "typ": "application/didcomm-plain+json", 114 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 115 | "body": {}, 116 | "attachments": [{"id": "1", "data": "None"}], 117 | } 118 | ), 119 | json_dumps( 120 | { 121 | "id": "1234567890", 122 | "typ": "application/didcomm-plain+json", 123 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 124 | "body": {}, 125 | "attachments": [{"id": "1", "data": "None"}], 126 | } 127 | ), 128 | # TODO: add more 129 | ] 130 | 131 | INVALID_PLAINTEXT_TEST_VECTORS = [ 132 | TTestVectorNegative(value, MalformedMessageError) for value in INVALID_MESSAGES 133 | ] 134 | -------------------------------------------------------------------------------- /tests/test_vectors/didcomm_messages/tests/test_vectors_signed_negative.py: -------------------------------------------------------------------------------- 1 | from didcomm.errors import MalformedMessageError 2 | from tests.test_vectors.common import TTestVectorNegative 3 | from tests.test_vectors.didcomm_messages.spec.spec_test_vectors_signed import ( 4 | TEST_SIGNED_DIDCOMM_MESSAGE_ALICE_KEY_1, 5 | ) 6 | from tests.test_vectors.didcomm_messages.tests.common import update 7 | 8 | INVALID_MESSAGES = [ 9 | update(TEST_SIGNED_DIDCOMM_MESSAGE_ALICE_KEY_1, "payload", "invalid"), 10 | # TODO: add more 11 | ] 12 | 13 | INVALID_SIGNED_TEST_VECTORS = [ 14 | TTestVectorNegative(value, MalformedMessageError) for value in INVALID_MESSAGES 15 | ] 16 | -------------------------------------------------------------------------------- /tests/test_vectors/secrets/__init__.py: -------------------------------------------------------------------------------- 1 | from .mock_secrets_resolver_alice import MockSecretsResolverAlice 2 | from .mock_secrets_resolver_bob import MockSecretsResolverBob 3 | from .mock_secrets_resolver_charlie import MockSecretsResolverCharlie 4 | from .mock_secrets_resolver_mediator1 import MockSecretsResolverMediator1 5 | from .mock_secrets_resolver_mediator2 import MockSecretsResolverMediator2 6 | 7 | 8 | __all__ = [ 9 | "MockSecretsResolverAlice", 10 | "MockSecretsResolverBob", 11 | "MockSecretsResolverCharlie", 12 | "MockSecretsResolverMediator1", 13 | "MockSecretsResolverMediator2", 14 | ] 15 | -------------------------------------------------------------------------------- /tests/test_vectors/secrets/mock_secrets_resolver.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from didcomm.common.types import DID_URL 4 | from didcomm import Secret, SecretsResolverInMemory 5 | 6 | 7 | class MockSecretsResolverInMemory(SecretsResolverInMemory): 8 | def __init__(self, secrets: List[Secret]): 9 | self._secrets_list = secrets 10 | super().__init__(secrets) 11 | 12 | def get_key_sync(self, kid: DID_URL) -> Optional[Secret]: 13 | return self._secrets.get(kid) 14 | 15 | def get_keys_sync(self, kids: List[DID_URL]) -> List[DID_URL]: 16 | return [s.kid for s in self._secrets.values() if s.kid in kids] 17 | 18 | def get_secrets(self) -> List[Secret]: 19 | return self._secrets_list 20 | 21 | def get_secret_kids(self) -> List[DID_URL]: 22 | return [s.kid for s in self._secrets_list] 23 | -------------------------------------------------------------------------------- /tests/test_vectors/secrets/mock_secrets_resolver_alice.py: -------------------------------------------------------------------------------- 1 | from authlib.common.encoding import json_dumps 2 | 3 | from didcomm import ( 4 | VerificationMethodType, 5 | VerificationMaterial, 6 | VerificationMaterialFormat, 7 | Secret, 8 | ) 9 | from tests.test_vectors.secrets.mock_secrets_resolver import MockSecretsResolverInMemory 10 | 11 | ALICE_SECRET_AUTH_KEY_ED25519 = Secret( 12 | kid="did:example:alice#key-1", 13 | type=VerificationMethodType.JSON_WEB_KEY_2020, 14 | verification_material=VerificationMaterial( 15 | format=VerificationMaterialFormat.JWK, 16 | value=json_dumps( 17 | { 18 | "kty": "OKP", 19 | "d": "pFRUKkyzx4kHdJtFSnlPA9WzqkDT1HWV0xZ5OYZd2SY", 20 | "crv": "Ed25519", 21 | "x": "G-boxFB6vOZBu-wXkm-9Lh79I8nf9Z50cILaOgKKGww", 22 | } 23 | ), 24 | ), 25 | ) 26 | 27 | ALICE_SECRET_AUTH_KEY_P256 = Secret( 28 | kid="did:example:alice#key-2", 29 | type=VerificationMethodType.JSON_WEB_KEY_2020, 30 | verification_material=VerificationMaterial( 31 | format=VerificationMaterialFormat.JWK, 32 | value=json_dumps( 33 | { 34 | "kty": "EC", 35 | "d": "7TCIdt1rhThFtWcEiLnk_COEjh1ZfQhM4bW2wz-dp4A", 36 | "crv": "P-256", 37 | "x": "2syLh57B-dGpa0F8p1JrO6JU7UUSF6j7qL-vfk1eOoY", 38 | "y": "BgsGtI7UPsObMRjdElxLOrgAO9JggNMjOcfzEPox18w", 39 | } 40 | ), 41 | ), 42 | ) 43 | 44 | ALICE_SECRET_AUTH_KEY_SECP256K1 = Secret( 45 | kid="did:example:alice#key-3", 46 | type=VerificationMethodType.JSON_WEB_KEY_2020, 47 | verification_material=VerificationMaterial( 48 | format=VerificationMaterialFormat.JWK, 49 | value=json_dumps( 50 | { 51 | "kty": "EC", 52 | "d": "N3Hm1LXA210YVGGsXw_GklMwcLu_bMgnzDese6YQIyA", 53 | "crv": "secp256k1", 54 | "x": "aToW5EaTq5mlAf8C5ECYDSkqsJycrW-e1SQ6_GJcAOk", 55 | "y": "JAGX94caA21WKreXwYUaOCYTBMrqaX4KWIlsQZTHWCk", 56 | } 57 | ), 58 | ), 59 | ) 60 | 61 | ALICE_SECRET_KEY_AGREEMENT_KEY_X25519 = Secret( 62 | kid="did:example:alice#key-x25519-1", 63 | type=VerificationMethodType.JSON_WEB_KEY_2020, 64 | verification_material=VerificationMaterial( 65 | format=VerificationMaterialFormat.JWK, 66 | value=json_dumps( 67 | { 68 | "kty": "OKP", 69 | "d": "r-jK2cO3taR8LQnJB1_ikLBTAnOtShJOsHXRUWT-aZA", 70 | "crv": "X25519", 71 | "x": "avH0O2Y4tqLAq8y9zpianr8ajii5m4F_mICrzNlatXs", 72 | } 73 | ), 74 | ), 75 | ) 76 | 77 | ALICE_SECRET_KEY_AGREEMENT_KEY_P256 = Secret( 78 | kid="did:example:alice#key-p256-1", 79 | type=VerificationMethodType.JSON_WEB_KEY_2020, 80 | verification_material=VerificationMaterial( 81 | format=VerificationMaterialFormat.JWK, 82 | value=json_dumps( 83 | { 84 | "kty": "EC", 85 | "d": "sB0bYtpaXyp-h17dDpMx91N3Du1AdN4z1FUq02GbmLw", 86 | "crv": "P-256", 87 | "x": "L0crjMN1g0Ih4sYAJ_nGoHUck2cloltUpUVQDhF2nHE", 88 | "y": "SxYgE7CmEJYi7IDhgK5jI4ZiajO8jPRZDldVhqFpYoo", 89 | } 90 | ), 91 | ), 92 | ) 93 | 94 | ALICE_SECRET_KEY_AGREEMENT_KEY_P521 = Secret( 95 | kid="did:example:alice#key-p521-1", 96 | type=VerificationMethodType.JSON_WEB_KEY_2020, 97 | verification_material=VerificationMaterial( 98 | format=VerificationMaterialFormat.JWK, 99 | value=json_dumps( 100 | { 101 | "kty": "EC", 102 | "d": "AQCQKE7rZpxPnX9RgjXxeywrAMp1fJsyFe4cir1gWj-8t8xWaM_E2qBkTTzyjbRBu-JPXHe_auT850iYmE34SkWi", 103 | "crv": "P-521", 104 | "x": "AHBEVPRhAv-WHDEvxVM9S0px9WxxwHL641Pemgk9sDdxvli9VpKCBdra5gg_4kupBDhz__AlaBgKOC_15J2Byptz", 105 | "y": "AciGcHJCD_yMikQvlmqpkBbVqqbg93mMVcgvXBYAQPP-u9AF7adybwZrNfHWCKAQwGF9ugd0Zhg7mLMEszIONFRk", 106 | } 107 | ), 108 | ), 109 | ) 110 | 111 | 112 | class MockSecretsResolverAlice(MockSecretsResolverInMemory): 113 | def __init__(self): 114 | super().__init__( 115 | secrets=[ 116 | ALICE_SECRET_AUTH_KEY_ED25519, 117 | ALICE_SECRET_AUTH_KEY_P256, 118 | ALICE_SECRET_AUTH_KEY_SECP256K1, 119 | ALICE_SECRET_KEY_AGREEMENT_KEY_X25519, 120 | ALICE_SECRET_KEY_AGREEMENT_KEY_P256, 121 | ALICE_SECRET_KEY_AGREEMENT_KEY_P521, 122 | ] 123 | ) 124 | -------------------------------------------------------------------------------- /tests/test_vectors/secrets/mock_secrets_resolver_charlie.py: -------------------------------------------------------------------------------- 1 | from authlib.common.encoding import json_dumps 2 | 3 | from didcomm import ( 4 | VerificationMethodType, 5 | VerificationMaterial, 6 | VerificationMaterialFormat, 7 | Secret, 8 | ) 9 | from tests.test_vectors.secrets.mock_secrets_resolver import MockSecretsResolverInMemory 10 | 11 | CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519 = Secret( 12 | kid="did:example:charlie#key-x25519-1", 13 | type=VerificationMethodType.JSON_WEB_KEY_2020, 14 | verification_material=VerificationMaterial( 15 | format=VerificationMaterialFormat.JWK, 16 | value=json_dumps( 17 | { 18 | "kty": "OKP", 19 | "d": "Z-BsgFe-eCvhuZlCBX5BV2XiDE2M92gkaORCe68YdZI", 20 | "crv": "X25519", 21 | "x": "nTiVFj7DChMsETDdxd5dIzLAJbSQ4j4UG6ZU1ogLNlw", 22 | } 23 | ), 24 | ), 25 | ) 26 | 27 | CHARLIE_SECRET_AUTH_KEY_ED25519 = Secret( 28 | kid="did:example:charlie#key-1", 29 | type=VerificationMethodType.JSON_WEB_KEY_2020, 30 | verification_material=VerificationMaterial( 31 | format=VerificationMaterialFormat.JWK, 32 | value=json_dumps( 33 | { 34 | "kty": "OKP", 35 | "d": "T2azVap7CYD_kB8ilbnFYqwwYb5N-GcD6yjGEvquZXg", 36 | "crv": "Ed25519", 37 | "x": "VDXDwuGKVq91zxU6q7__jLDUq8_C5cuxECgd-1feFTE", 38 | } 39 | ), 40 | ), 41 | ) 42 | 43 | 44 | class MockSecretsResolverCharlie(MockSecretsResolverInMemory): 45 | def __init__(self): 46 | super().__init__( 47 | secrets=[ 48 | CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519, 49 | CHARLIE_SECRET_AUTH_KEY_ED25519, 50 | ] 51 | ) 52 | -------------------------------------------------------------------------------- /tests/test_vectors/secrets/mock_secrets_resolver_charlie_rotated_to_alice.py: -------------------------------------------------------------------------------- 1 | from tests.test_vectors.secrets.mock_secrets_resolver import MockSecretsResolverInMemory 2 | from tests.test_vectors.secrets.mock_secrets_resolver_alice import ( 3 | ALICE_SECRET_AUTH_KEY_ED25519, 4 | ALICE_SECRET_AUTH_KEY_P256, 5 | ALICE_SECRET_AUTH_KEY_SECP256K1, 6 | ALICE_SECRET_KEY_AGREEMENT_KEY_X25519, 7 | ALICE_SECRET_KEY_AGREEMENT_KEY_P256, 8 | ALICE_SECRET_KEY_AGREEMENT_KEY_P521, 9 | ) 10 | from tests.test_vectors.secrets.mock_secrets_resolver_charlie import ( 11 | CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519, 12 | CHARLIE_SECRET_AUTH_KEY_ED25519, 13 | ) 14 | 15 | 16 | class MockSecretsResolverCharlieRotatedToAlice(MockSecretsResolverInMemory): 17 | def __init__(self): 18 | super().__init__( 19 | secrets=[ 20 | CHARLIE_SECRET_KEY_AGREEMENT_KEY_X25519, 21 | CHARLIE_SECRET_AUTH_KEY_ED25519, 22 | ALICE_SECRET_AUTH_KEY_ED25519, 23 | ALICE_SECRET_AUTH_KEY_P256, 24 | ALICE_SECRET_AUTH_KEY_SECP256K1, 25 | ALICE_SECRET_KEY_AGREEMENT_KEY_X25519, 26 | ALICE_SECRET_KEY_AGREEMENT_KEY_P256, 27 | ALICE_SECRET_KEY_AGREEMENT_KEY_P521, 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /tests/test_vectors/secrets/mock_secrets_resolver_mediator1.py: -------------------------------------------------------------------------------- 1 | from authlib.common.encoding import json_dumps 2 | 3 | from didcomm import ( 4 | VerificationMethodType, 5 | VerificationMaterial, 6 | VerificationMaterialFormat, 7 | Secret, 8 | ) 9 | from tests.test_vectors.secrets.mock_secrets_resolver import MockSecretsResolverInMemory 10 | 11 | # FIXME build verification material 12 | # (currently it's a copy-paste from Bob's ones) 13 | MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_X25519_1 = Secret( 14 | kid="did:example:mediator1#key-x25519-1", 15 | type=VerificationMethodType.JSON_WEB_KEY_2020, 16 | verification_material=VerificationMaterial( 17 | format=VerificationMaterialFormat.JWK, 18 | value=json_dumps( 19 | { 20 | "kty": "OKP", 21 | "d": "b9NnuOCB0hm7YGNvaE9DMhwH_wjZA1-gWD6dA0JWdL0", 22 | "crv": "X25519", 23 | "x": "GDTrI66K0pFfO54tlCSvfjjNapIs44dzpneBgyx0S3E", 24 | } 25 | ), 26 | ), 27 | ) 28 | 29 | MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_P256_1 = Secret( 30 | kid="did:example:mediator1#key-p256-1", 31 | type=VerificationMethodType.JSON_WEB_KEY_2020, 32 | verification_material=VerificationMaterial( 33 | format=VerificationMaterialFormat.JWK, 34 | value=json_dumps( 35 | { 36 | "kty": "EC", 37 | "d": "PgwHnlXxt8pwR6OCTUwwWx-P51BiLkFZyqHzquKddXQ", 38 | "crv": "P-256", 39 | "x": "FQVaTOksf-XsCUrt4J1L2UGvtWaDwpboVlqbKBY2AIo", 40 | "y": "6XFB9PYo7dyC5ViJSO9uXNYkxTJWn0d_mqJ__ZYhcNY", 41 | } 42 | ), 43 | ), 44 | ) 45 | 46 | MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_P384_1 = Secret( 47 | kid="did:example:mediator1#key-p384-1", 48 | type=VerificationMethodType.JSON_WEB_KEY_2020, 49 | verification_material=VerificationMaterial( 50 | format=VerificationMaterialFormat.JWK, 51 | value=json_dumps( 52 | { 53 | "kty": "EC", 54 | "d": "ajqcWbYA0UDBKfAhkSkeiVjMMt8l-5rcknvEv9t_Os6M8s-HisdywvNCX4CGd_xY", 55 | "crv": "P-384", 56 | "x": "MvnE_OwKoTcJVfHyTX-DLSRhhNwlu5LNoQ5UWD9Jmgtdxp_kpjsMuTTBnxg5RF_Y", 57 | "y": "X_3HJBcKFQEG35PZbEOBn8u9_z8V1F9V1Kv-Vh0aSzmH-y9aOuDJUE3D4Hvmi5l7", 58 | } 59 | ), 60 | ), 61 | ) 62 | 63 | MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_P521_1 = Secret( 64 | kid="did:example:mediator1#key-p521-1", 65 | type=VerificationMethodType.JSON_WEB_KEY_2020, 66 | verification_material=VerificationMaterial( 67 | format=VerificationMaterialFormat.JWK, 68 | value=json_dumps( 69 | { 70 | "kty": "EC", 71 | "d": "AV5ocjvy7PkPgNrSuvCxtG70NMj6iTabvvjSLbsdd8OdI9HlXYlFR7RdBbgLUTruvaIRhjEAE9gNTH6rWUIdfuj6", 72 | "crv": "P-521", 73 | "x": "Af9O5THFENlqQbh2Ehipt1Yf4gAd9RCa3QzPktfcgUIFADMc4kAaYVViTaDOuvVS2vMS1KZe0D5kXedSXPQ3QbHi", 74 | "y": "ATZVigRQ7UdGsQ9j-omyff6JIeeUv3CBWYsZ0l6x3C_SYqhqVV7dEG-TafCCNiIxs8qeUiXQ8cHWVclqkH4Lo1qH", 75 | } 76 | ), 77 | ), 78 | ) 79 | 80 | 81 | class MockSecretsResolverMediator1(MockSecretsResolverInMemory): 82 | def __init__(self): 83 | super().__init__( 84 | secrets=[ 85 | MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_X25519_1, 86 | MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_P256_1, 87 | MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_P384_1, 88 | MEDIATOR1_SECRET_KEY_AGREEMENT_KEY_P521_1, 89 | ] 90 | ) 91 | -------------------------------------------------------------------------------- /tests/test_vectors/secrets/mock_secrets_resolver_mediator2.py: -------------------------------------------------------------------------------- 1 | from authlib.common.encoding import json_dumps 2 | 3 | from didcomm import ( 4 | VerificationMethodType, 5 | VerificationMaterial, 6 | VerificationMaterialFormat, 7 | Secret, 8 | ) 9 | from tests.test_vectors.secrets.mock_secrets_resolver import MockSecretsResolverInMemory 10 | 11 | # FIXME build verification material 12 | # (currently it's a copy-paste from Bob's ones) 13 | MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_X25519_1 = Secret( 14 | kid="did:example:mediator2#key-x25519-1", 15 | type=VerificationMethodType.JSON_WEB_KEY_2020, 16 | verification_material=VerificationMaterial( 17 | format=VerificationMaterialFormat.JWK, 18 | value=json_dumps( 19 | { 20 | "kty": "OKP", 21 | "d": "p-vteoF1gopny1HXywt76xz_uC83UUmrgszsI-ThBKk", 22 | "crv": "X25519", 23 | "x": "UT9S3F5ep16KSNBBShU2wh3qSfqYjlasZimn0mB8_VM", 24 | } 25 | ), 26 | ), 27 | ) 28 | 29 | MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_P256_1 = Secret( 30 | kid="did:example:mediator2#key-p256-1", 31 | type=VerificationMethodType.JSON_WEB_KEY_2020, 32 | verification_material=VerificationMaterial( 33 | format=VerificationMaterialFormat.JWK, 34 | value=json_dumps( 35 | { 36 | "kty": "EC", 37 | "d": "agKz7HS8mIwqO40Q2dwm_Zi70IdYFtonN5sZecQoxYU", 38 | "crv": "P-256", 39 | "x": "n0yBsGrwGZup9ywKhzD4KoORGicilzIUyfcXb1CSwe0", 40 | "y": "ov0buZJ8GHzV128jmCw1CaFbajZoFFmiJDbMrceCXIw", 41 | } 42 | ), 43 | ), 44 | ) 45 | 46 | MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_P384_1 = Secret( 47 | kid="did:example:mediator2#key-p384-1", 48 | type=VerificationMethodType.JSON_WEB_KEY_2020, 49 | verification_material=VerificationMaterial( 50 | format=VerificationMaterialFormat.JWK, 51 | value=json_dumps( 52 | { 53 | "kty": "EC", 54 | "d": "OiwhRotK188BtbQy0XBO8PljSKYI6CCD-nE_ZUzK7o81tk3imDOuQ-jrSWaIkI-T", 55 | "crv": "P-384", 56 | "x": "2x3HOTvR8e-Tu6U4UqMd1wUWsNXMD0RgIunZTMcZsS-zWOwDgsrhYVHmv3k_DjV3", 57 | "y": "W9LLaBjlWYcXUxOf6ECSfcXKaC3-K9z4hCoP0PS87Q_4ExMgIwxVCXUEB6nf0GDd", 58 | } 59 | ), 60 | ), 61 | ) 62 | 63 | MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_P521_1 = Secret( 64 | kid="did:example:mediator2#key-p521-1", 65 | type=VerificationMethodType.JSON_WEB_KEY_2020, 66 | verification_material=VerificationMaterial( 67 | format=VerificationMaterialFormat.JWK, 68 | value=json_dumps( 69 | { 70 | "kty": "EC", 71 | "d": "ABixMEZHsyT7SRw-lY5HxdNOofTZLlwBHwPEJ3spEMC2sWN1RZQylZuvoyOBGJnPxg4-H_iVhNWf_OtgYODrYhCk", 72 | "crv": "P-521", 73 | "x": "ATp_WxCfIK_SriBoStmA0QrJc2pUR1djpen0VdpmogtnKxJbitiPq-HJXYXDKriXfVnkrl2i952MsIOMfD2j0Ots", 74 | "y": "AEJipR0Dc-aBZYDqN51SKHYSWs9hM58SmRY1MxgXANgZrPaq1EeGMGOjkbLMEJtBThdjXhkS5VlXMkF0cYhZELiH", 75 | } 76 | ), 77 | ), 78 | ) 79 | 80 | 81 | class MockSecretsResolverMediator2(MockSecretsResolverInMemory): 82 | def __init__(self): 83 | super().__init__( 84 | secrets=[ 85 | MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_X25519_1, 86 | MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_P256_1, 87 | MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_P384_1, 88 | MEDIATOR2_SECRET_KEY_AGREEMENT_KEY_P521_1, 89 | ] 90 | ) 91 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/common.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from authlib.common.encoding import json_loads 4 | 5 | from didcomm.core.utils import parse_base64url_encoded_json 6 | from didcomm.unpack import unpack, Metadata 7 | from tests.test_vectors.common import TTestVector 8 | from tests.test_vectors.didcomm_messages.messages import TEST_MESSAGE 9 | 10 | 11 | def decode_and_remove_jws_signatures(jws: str) -> dict: 12 | jws = json_loads(jws) 13 | jws["payload"] = parse_base64url_encoded_json(jws["payload"]) 14 | for s in jws["signatures"]: 15 | s["protected"] = parse_base64url_encoded_json(s["protected"]) 16 | del s["signature"] 17 | return jws 18 | 19 | 20 | def decode_jwe_headers(jwe: str) -> dict: 21 | jwe = json_loads(jwe) 22 | protected = parse_base64url_encoded_json(jwe["protected"]) 23 | del protected["epk"] 24 | recipients = jwe["recipients"] 25 | for r in recipients: 26 | del r["encrypted_key"] 27 | return {"protected": protected, "recipients": recipients} 28 | 29 | 30 | def remove_signed_msg(metadata: Metadata) -> Metadata: 31 | metadata = copy.deepcopy(metadata) 32 | metadata.signed_message = None 33 | return metadata 34 | 35 | 36 | async def check_unpack_test_vector(test_vector: TTestVector, resolvers_config): 37 | unpack_result = await unpack(resolvers_config, test_vector.value) 38 | assert unpack_result.message == TEST_MESSAGE 39 | assert unpack_result.metadata == test_vector.metadata 40 | -------------------------------------------------------------------------------- /tests/unit/conftest.py: -------------------------------------------------------------------------------- 1 | from string import ascii_letters 2 | import random 3 | 4 | import pytest 5 | 6 | from didcomm.common.types import DID, DID_URL 7 | from didcomm.did_doc.did_doc import DIDDoc, DIDCommService 8 | from didcomm.pack_encrypted import ( 9 | PackEncryptedConfig, 10 | PackEncryptedParameters, 11 | ) 12 | from didcomm.common.resolvers import ResolversConfig 13 | 14 | from tests import mock_module 15 | 16 | 17 | # ============== 18 | # unittest mocks 19 | # ============== 20 | 21 | 22 | @pytest.fixture 23 | def did() -> DID: 24 | return "did:example:1" 25 | 26 | 27 | @pytest.fixture 28 | def did1(did) -> DID: 29 | return did 30 | 31 | 32 | @pytest.fixture 33 | def did2(did) -> DID: 34 | return did.replace("1", "2") 35 | 36 | 37 | @pytest.fixture 38 | def did3(did) -> DID: 39 | return did.replace("1", "3") 40 | 41 | 42 | @pytest.fixture 43 | def did_url(did) -> DID_URL: 44 | return "{did}#somekey1" 45 | 46 | 47 | def build_didcomm_service( 48 | id=None, 49 | service_endpoint="https://example.com/endpoint", 50 | routing_keys=None, 51 | recipient_keys=None, 52 | accept=None, 53 | ) -> DIDCommService: 54 | return DIDCommService( 55 | # id needs to be unique, so we generate 8 letters random fragment 56 | id=id 57 | or "did:example:some_did#" 58 | + "".join(random.choice(ascii_letters) for i in range(8)), 59 | service_endpoint=service_endpoint, 60 | routing_keys=routing_keys or [], 61 | recipient_keys=recipient_keys or [], 62 | accept=accept or [], 63 | ) 64 | 65 | 66 | @pytest.fixture 67 | def didcomm_service(): 68 | return build_didcomm_service( 69 | id="did:example:some_did#service", 70 | service_endpoint="https://example.com/endpoint", 71 | routing_keys=[ 72 | "did:example:some_did#key-1", 73 | "did:example:some_did#key-2", 74 | "did:example:some_did#key-3", 75 | ], 76 | accept=["didcomm/v2"], 77 | ) 78 | 79 | 80 | @pytest.fixture 81 | def didcomm_service1(didcomm_service): 82 | return didcomm_service 83 | 84 | 85 | @pytest.fixture 86 | def didcomm_service2(didcomm_service1): 87 | return build_didcomm_service( 88 | id=didcomm_service1.id.replace("1", "2"), 89 | service_endpoint=didcomm_service1.service_endpoint.replace("1", "2"), 90 | routing_keys=[ 91 | "did:example:some_did#key-4", 92 | "did:example:some_did#key-5", 93 | "did:example:some_did#key-6", 94 | ], 95 | accept=[""], 96 | ) 97 | 98 | 99 | @pytest.fixture 100 | def didcomm_service3(didcomm_service1): 101 | return build_didcomm_service( 102 | id=didcomm_service1.id.replace("1", "3"), 103 | service_endpoint=didcomm_service1.service_endpoint.replace("1", "3"), 104 | routing_keys=[ 105 | "did:example:some_did#key-7", 106 | "did:example:some_did#key-8", 107 | "did:example:some_did#key-9", 108 | ], 109 | accept=[""], 110 | ) 111 | 112 | 113 | def build_did_doc( 114 | did="did:example:1", 115 | key_agreement=None, 116 | authentication=None, 117 | verification_method=None, 118 | service=None, 119 | ) -> DIDDoc: 120 | return DIDDoc( 121 | id=did, 122 | key_agreement=key_agreement or [], 123 | authentication=authentication or [], 124 | verification_method=verification_method or [], 125 | service=service or [], 126 | ) 127 | 128 | 129 | @pytest.fixture 130 | def did_doc(did) -> DIDDoc: 131 | return build_did_doc(did) 132 | 133 | 134 | @pytest.fixture 135 | def pack_config() -> PackEncryptedConfig: 136 | return PackEncryptedConfig() 137 | 138 | 139 | @pytest.fixture 140 | def pack_params() -> PackEncryptedParameters: 141 | return PackEncryptedParameters() 142 | 143 | 144 | @pytest.fixture 145 | def resolvers_config_mock(mocker) -> ResolversConfig: 146 | secrets_resolver = mocker.Mock() 147 | did_resolver = mocker.Mock() 148 | # NOTE AsyncMock from unittest.mock is availble only in python 3.8+ 149 | # - here we rely on PyPI backport https://pypi.org/project/mock/ 150 | # - the module supports python 3.6+ 151 | did_resolver.resolve = mock_module.AsyncMock() 152 | return ResolversConfig(secrets_resolver, did_resolver) 153 | -------------------------------------------------------------------------------- /tests/unit/didcomm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/unit/didcomm/__init__.py -------------------------------------------------------------------------------- /tests/unit/didcomm/message.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import attr 3 | 4 | from didcomm.core import converters 5 | from didcomm.message import GenericMessage 6 | 7 | 8 | @pytest.mark.parametrize( 9 | "m_id, m_id_expected", 10 | [ 11 | pytest.param("123", "123", id="str"), 12 | pytest.param(lambda: "345", "345", id="function"), 13 | pytest.param(None, None, id="default"), 14 | ], 15 | ) 16 | def test_forward_message__id_good(m_id, m_id_expected, mocker): 17 | if m_id is None: 18 | # XXX mocks in-place of imports doesn't work for attrs calsses 19 | # by some reason 20 | spy = mocker.spy(converters, "didcomm_id_generator_default") 21 | msg = GenericMessage(type="test_type", body="test_body") 22 | assert spy.call_count == 1 23 | assert msg.id == spy.spy_return 24 | else: 25 | msg = GenericMessage(type="test_type", body="test_body") 26 | assert attr.evolve(msg, **dict(id=m_id)).id == m_id_expected 27 | -------------------------------------------------------------------------------- /tests/unit/didcomm/protocols/routing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/unit/didcomm/protocols/routing/__init__.py -------------------------------------------------------------------------------- /tests/unit/didcomm/protocols/routing/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.protocols.routing.forward import ForwardMessage 4 | 5 | from .helper import gen_fwd_msg 6 | 7 | 8 | @pytest.fixture 9 | def fwd_msg() -> ForwardMessage: 10 | return gen_fwd_msg() 11 | -------------------------------------------------------------------------------- /tests/unit/didcomm/protocols/routing/helper.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any, List, Dict 2 | 3 | from didcomm.message import Attachment, AttachmentDataJson 4 | 5 | from didcomm.protocols.routing.forward import ForwardBody, ForwardMessage 6 | 7 | 8 | # TODO add more if needed 9 | diff_type_objects = ["1", 2, [3], (4,), {5: 6}, lambda: "7", (8 == 9), {10}] 10 | 11 | 12 | def gen_fwd_msg(): 13 | return ForwardMessage( 14 | body=ForwardBody(next="did:example:123"), 15 | attachments=[Attachment(data=AttachmentDataJson({"some": "msg"}))], 16 | ) 17 | 18 | 19 | def gen_fwd_msg_dict( 20 | update: Optional[Dict[str, Any]] = None, remove: Optional[List[str]] = None 21 | ): 22 | remove = remove or [] 23 | update = update or {} 24 | 25 | res = gen_fwd_msg().as_dict() 26 | res.update(update) 27 | 28 | for i in remove: 29 | del res[i] 30 | 31 | return res 32 | -------------------------------------------------------------------------------- /tests/unit/didcomm/protocols/routing/test_forward_message.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import attr 3 | from typing import Callable 4 | 5 | from didcomm.errors import DIDCommValueError, MalformedMessageError 6 | from didcomm.core.types import DIDCOMM_ORG_DOMAIN, DIDCommFields 7 | from didcomm.message import Attachment, AttachmentDataJson, AttachmentDataLinks 8 | from didcomm.protocols.routing.forward import ( 9 | ForwardBody, 10 | ForwardMessage, 11 | ROUTING_PROTOCOL_NAME, 12 | ROUTING_PROTOCOL_VER_CURRENT, 13 | ROUTING_PROTOCOL_MSG_TYPES, 14 | ) 15 | 16 | from .helper import diff_type_objects, gen_fwd_msg, gen_fwd_msg_dict 17 | 18 | 19 | # TODO add tests for ForwardBody 20 | # def test_forward_body__(m_id): 21 | # pass 22 | 23 | 24 | # TODO test that Callable[[], ] is also bad 25 | @pytest.mark.parametrize( 26 | "m_id", 27 | [o for o in diff_type_objects if not isinstance(o, (str, Callable))], 28 | ids=lambda x: type(x).__name__, 29 | ) 30 | def test_forward_message__id_bad(m_id, fwd_msg): 31 | with pytest.raises(DIDCommValueError): 32 | attr.evolve(fwd_msg, **dict(id=m_id)) 33 | 34 | 35 | def _build_mturi( 36 | scheme="https", 37 | domain=DIDCOMM_ORG_DOMAIN, 38 | prot=ROUTING_PROTOCOL_NAME, 39 | ver=ROUTING_PROTOCOL_VER_CURRENT, 40 | msg_t=ROUTING_PROTOCOL_MSG_TYPES.FORWARD.value, 41 | ): 42 | return f"{scheme}://{domain}/{prot}/{ver}/{msg_t}" 43 | 44 | 45 | # TODO cover exception messages as well 46 | @pytest.mark.parametrize( 47 | "msg_t, exc_t", 48 | [ 49 | pytest.param(123, DIDCommValueError, id="int"), 50 | pytest.param(_build_mturi(scheme="tcp"), DIDCommValueError, id="scheme"), 51 | pytest.param( 52 | _build_mturi(domain="somedomain.org"), DIDCommValueError, id="domain" 53 | ), 54 | pytest.param( 55 | _build_mturi(prot="someprotocol"), DIDCommValueError, id="protocol" 56 | ), 57 | pytest.param( 58 | _build_mturi(ver="1.9"), DIDCommValueError, id="too_old_version_1.9" 59 | ), 60 | pytest.param( 61 | _build_mturi(ver="3.0"), DIDCommValueError, id="too_new_version_3.0" 62 | ), 63 | pytest.param(_build_mturi(msg_t="somemsg"), DIDCommValueError, id="message_t"), 64 | ], 65 | ) 66 | def test_forward_message__type_bad(msg_t, exc_t, fwd_msg): 67 | with pytest.raises(exc_t): 68 | attr.evolve(fwd_msg, **dict(type=msg_t)) 69 | 70 | 71 | @pytest.mark.parametrize("msg_ver", ["2.0", "2.0.1", "2.7.9"], ids=lambda x: f"v{x}") 72 | def test_forward_message__type_good(msg_ver, fwd_msg): 73 | msg_t = _build_mturi(ver=msg_ver) 74 | assert attr.evolve(fwd_msg, **dict(type=msg_t)).type == msg_t 75 | 76 | 77 | @pytest.mark.parametrize( 78 | "msg", 79 | [ 80 | pytest.param(gen_fwd_msg_dict(remove=[DIDCommFields.ID]), id="no_id"), 81 | pytest.param(gen_fwd_msg_dict(remove=[DIDCommFields.TYPE]), id="no_type"), 82 | pytest.param(gen_fwd_msg_dict(remove=[DIDCommFields.BODY]), id="no_body"), 83 | # TODO the cases above better to test in scope of GenericMessage 84 | pytest.param(gen_fwd_msg_dict(update={DIDCommFields.BODY: {}}), id="no_next"), 85 | pytest.param(gen_fwd_msg_dict(remove=[DIDCommFields.ATTACHMENTS]), id="no_att"), 86 | pytest.param( 87 | gen_fwd_msg_dict(update={DIDCommFields.ATTACHMENTS: []}), id="empty_att" 88 | ), 89 | pytest.param( 90 | gen_fwd_msg_dict( 91 | update={ 92 | DIDCommFields.ATTACHMENTS: [ 93 | Attachment(data=AttachmentDataJson({"some": "msg1"})), 94 | Attachment(data=AttachmentDataJson({"some": "msg2"})), 95 | ] 96 | } 97 | ), 98 | id="bad_att_len", 99 | ), 100 | pytest.param( 101 | gen_fwd_msg_dict( 102 | update={ 103 | DIDCommFields.ATTACHMENTS: [ 104 | Attachment(data=AttachmentDataLinks(["link"], "hash")) 105 | ] 106 | } 107 | ), 108 | id="bad_att_type", 109 | ), 110 | ], 111 | ) 112 | def test_forward_message_from_dict__bad_msg(msg): 113 | with pytest.raises(MalformedMessageError): 114 | assert not ForwardMessage.from_dict(msg) 115 | 116 | 117 | def test_forward_message__body_from_dict(did): 118 | body = {DIDCommFields.NEXT: did} 119 | fwd_body = ForwardMessage._body_from_dict(body) 120 | assert isinstance(fwd_body, ForwardBody) 121 | assert fwd_body.next == body["next"] 122 | 123 | 124 | def test_forward_message__forwarded_msg(did): 125 | msg = gen_fwd_msg() 126 | assert msg.forwarded_msg == msg.attachments[0].data.json 127 | -------------------------------------------------------------------------------- /tests/unit/didcomm/protocols/routing/test_is_forward.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.protocols.routing import forward 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "msg, method", 8 | [({}, "from_dict"), ("123", "from_json"), (b"456", "from_json")], 9 | ids=["dict", "str", "bytes"], 10 | ) 11 | def test_is_forward__logic(mocker, msg, method): 12 | mock = mocker.patch.object(forward.ForwardMessage, method) 13 | forward.is_forward(msg) 14 | mock.assert_called_once_with(msg) 15 | -------------------------------------------------------------------------------- /tests/unit/didcomm/protocols/routing/test_unpack_forward.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.errors import MalformedMessageError 4 | from didcomm.common.types import DID 5 | from didcomm.core.defaults import DEF_ENC_ALG_ANON 6 | from didcomm.core.types import UnpackAnoncryptResult 7 | from didcomm.core.serialization import dict_to_json_bytes, dict_to_json 8 | from didcomm.protocols.routing import forward 9 | from didcomm.protocols.routing.forward import ( 10 | unpack_forward, 11 | ForwardMessage, 12 | ForwardResult, 13 | ) 14 | 15 | 16 | @pytest.fixture 17 | def unpack_anoncrypt_result(did: DID, fwd_msg: ForwardMessage) -> UnpackAnoncryptResult: 18 | return UnpackAnoncryptResult( 19 | msg=dict_to_json_bytes(fwd_msg.as_dict()), 20 | to_kids=[did, did + "2"], 21 | alg=DEF_ENC_ALG_ANON, 22 | ) 23 | 24 | 25 | @pytest.fixture 26 | def unpack_anoncrypt_mock(mocker, unpack_anoncrypt_result: UnpackAnoncryptResult): 27 | mock = mocker.patch.object(forward, "unpack_anoncrypt", autospec=True) 28 | mock.return_value = unpack_anoncrypt_result 29 | return mock 30 | 31 | 32 | @pytest.fixture 33 | def any_msg_dict(): 34 | return {"we": "dontcare"} 35 | 36 | 37 | @pytest.fixture 38 | def any_msg_json(any_msg_dict): 39 | return dict_to_json(any_msg_dict) 40 | 41 | 42 | # THE TESTS 43 | 44 | 45 | @pytest.mark.asyncio 46 | async def test_unpack_forward__unpack_anoncrypt_callspec( 47 | resolvers_config_mock, unpack_anoncrypt_mock, any_msg_dict, any_msg_json 48 | ): 49 | # to make the logic here: we just check the callspec 50 | unpack_anoncrypt_mock.side_effect = TypeError 51 | callspec = (any_msg_dict, resolvers_config_mock, True) 52 | try: 53 | await unpack_forward(resolvers_config_mock, any_msg_json, True) 54 | except TypeError: 55 | unpack_anoncrypt_mock.assert_called_once_with(*callspec) 56 | 57 | 58 | @pytest.mark.asyncio 59 | async def test_unpack_forward__no_forward_packed( 60 | resolvers_config_mock, unpack_anoncrypt_result, unpack_anoncrypt_mock 61 | ): 62 | # to hack / workaround Attachment frozen setting 63 | object.__setattr__( 64 | unpack_anoncrypt_result, "msg", dict_to_json_bytes({"key": "value"}) 65 | ) 66 | with pytest.raises(MalformedMessageError): 67 | await unpack_forward( 68 | resolvers_config_mock, dict_to_json({"we": "dontcare"}), True 69 | ) 70 | 71 | 72 | @pytest.mark.asyncio 73 | async def test_unpack_forward__return( 74 | resolvers_config_mock, 75 | unpack_anoncrypt_mock, 76 | unpack_anoncrypt_result, 77 | any_msg_json, 78 | fwd_msg, 79 | ): 80 | res = await unpack_forward(resolvers_config_mock, any_msg_json, True) 81 | 82 | assert isinstance(res, ForwardResult) 83 | assert res.forward_msg == fwd_msg 84 | assert res.forwarded_msg == fwd_msg.attachments[0].data.json 85 | assert res.forwarded_msg_encrypted_to == unpack_anoncrypt_result.to_kids 86 | -------------------------------------------------------------------------------- /tests/unit/keys/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/unit/keys/__init__.py -------------------------------------------------------------------------------- /tests/unit/keys/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture() 5 | def resolvers_config_alice(resolvers_config_alice_with_non_secrets): 6 | return resolvers_config_alice_with_non_secrets 7 | 8 | 9 | @pytest.fixture() 10 | def resolvers_config_bob(resolvers_config_bob_with_non_secrets): 11 | return resolvers_config_bob_with_non_secrets 12 | 13 | 14 | @pytest.fixture() 15 | def did_resolver(did_resolver_with_non_secrets): 16 | return did_resolver_with_non_secrets 17 | -------------------------------------------------------------------------------- /tests/unit/keys/test_sign_keys_selector.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.core.keys.sign_keys_selector import find_signing_key, find_verification_key 4 | from didcomm.did_doc.did_doc import VerificationMethod 5 | from didcomm.errors import ( 6 | DIDDocNotResolvedError, 7 | SecretNotFoundError, 8 | DIDUrlNotFoundError, 9 | ) 10 | from didcomm.secrets.secrets_resolver import Secret 11 | from tests.test_vectors.common import ALICE_DID 12 | from tests.test_vectors.utils import ( 13 | Person, 14 | get_auth_secrets, 15 | get_auth_methods_not_in_secrets, 16 | get_auth_methods, 17 | ) 18 | 19 | 20 | @pytest.mark.asyncio 21 | async def test_find_signing_key_by_did_positive(resolvers_config_alice): 22 | secret = await find_signing_key(ALICE_DID, resolvers_config_alice) 23 | # the first found secret is returned 24 | assert secret == get_auth_secrets(Person.ALICE)[0] 25 | 26 | 27 | @pytest.mark.asyncio 28 | async def test_find_signing_key_by_kid_positive(resolvers_config_alice): 29 | for secret in get_auth_secrets(Person.ALICE): 30 | await check_find_signing_key_by_kid(secret, resolvers_config_alice) 31 | 32 | 33 | async def check_find_signing_key_by_kid( 34 | expected_secret: Secret, resolvers_config_alice 35 | ): 36 | assert ( 37 | await find_signing_key(expected_secret.kid, resolvers_config_alice) 38 | == expected_secret 39 | ) 40 | 41 | 42 | @pytest.mark.asyncio 43 | async def test_find_signing_key_by_did_unknown_did(resolvers_config_alice): 44 | with pytest.raises(DIDDocNotResolvedError): 45 | await find_signing_key("did:example:unknown", resolvers_config_alice) 46 | 47 | 48 | @pytest.mark.asyncio 49 | async def test_find_signing_key_by_kid_unknown_did(resolvers_config_alice): 50 | with pytest.raises(SecretNotFoundError): 51 | await find_signing_key("did:example:unknown#key-1", resolvers_config_alice) 52 | 53 | 54 | @pytest.mark.asyncio 55 | async def test_find_signing_key_by_kid_secret_not_found(resolvers_config_alice): 56 | for vm in get_auth_methods_not_in_secrets(Person.ALICE): 57 | with pytest.raises(SecretNotFoundError): 58 | await find_signing_key(vm.id, resolvers_config_alice) 59 | 60 | 61 | @pytest.mark.asyncio 62 | async def test_find_signing_key_by_kid_unknown_kid(resolvers_config_alice): 63 | with pytest.raises(SecretNotFoundError): 64 | await find_signing_key(ALICE_DID + "#unknown-key-1", resolvers_config_alice) 65 | 66 | 67 | @pytest.mark.asyncio 68 | async def test_find_verification_key_positive(resolvers_config_alice): 69 | for vm in get_auth_methods(Person.ALICE): 70 | await check_find_verification_key_by_kid(vm, resolvers_config_alice) 71 | 72 | 73 | async def check_find_verification_key_by_kid( 74 | expected_ver_method: VerificationMethod, resolvers_config_alice 75 | ): 76 | verification_method = await find_verification_key( 77 | expected_ver_method.id, resolvers_config_alice 78 | ) 79 | assert verification_method == expected_ver_method 80 | 81 | 82 | @pytest.mark.asyncio 83 | async def test_find_verification_key_unknown_did(resolvers_config_alice): 84 | with pytest.raises(DIDDocNotResolvedError): 85 | await find_verification_key("did:example:unknown#key-1", resolvers_config_alice) 86 | 87 | 88 | @pytest.mark.asyncio 89 | async def test_find_verification_key_unknown_kid(resolvers_config_alice): 90 | with pytest.raises(DIDUrlNotFoundError): 91 | await find_verification_key( 92 | ALICE_DID + "#unknown-key-1", resolvers_config_alice 93 | ) 94 | -------------------------------------------------------------------------------- /tests/unit/pack_common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/unit/pack_common/__init__.py -------------------------------------------------------------------------------- /tests/unit/pack_common/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture() 5 | def resolvers_config_alice(resolvers_config_alice_with_non_secrets): 6 | return resolvers_config_alice_with_non_secrets 7 | 8 | 9 | @pytest.fixture() 10 | def resolvers_config_bob(resolvers_config_bob_with_non_secrets): 11 | return resolvers_config_bob_with_non_secrets 12 | 13 | 14 | @pytest.fixture() 15 | def did_resolver(did_resolver_with_non_secrets): 16 | return did_resolver_with_non_secrets 17 | -------------------------------------------------------------------------------- /tests/unit/pack_encrypted/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/unit/pack_encrypted/__init__.py -------------------------------------------------------------------------------- /tests/unit/pack_encrypted/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.common.resolvers import ResolversConfig 4 | from didcomm.secrets.secrets_resolver_in_memory import SecretsResolverInMemory 5 | from tests.test_vectors.secrets.mock_secrets_resolver_alice import ( 6 | MockSecretsResolverAlice, 7 | ) 8 | from tests.test_vectors.secrets.mock_secrets_resolver_charlie import ( 9 | MockSecretsResolverCharlie, 10 | ) 11 | 12 | 13 | @pytest.fixture() 14 | def resolvers_config_alice(resolvers_config_alice_with_non_secrets): 15 | return resolvers_config_alice_with_non_secrets 16 | 17 | 18 | @pytest.fixture() 19 | def resolvers_config_bob(resolvers_config_bob_with_non_secrets): 20 | return resolvers_config_bob_with_non_secrets 21 | 22 | 23 | @pytest.fixture() 24 | def did_resolver(did_resolver_with_non_secrets): 25 | return did_resolver_with_non_secrets 26 | 27 | 28 | class MockSecretsResolverAliceNewDid(SecretsResolverInMemory): 29 | def __init__(self): 30 | super().__init__( 31 | secrets=list(MockSecretsResolverAlice()._secrets.values()) 32 | + list(MockSecretsResolverCharlie()._secrets.values()) 33 | ) 34 | 35 | 36 | @pytest.fixture() 37 | def secrets_resolver_alice_with_new_did(): 38 | return MockSecretsResolverAliceNewDid() 39 | 40 | 41 | @pytest.fixture() 42 | def resolvers_config_alice_with_new_did( 43 | resolvers_config_alice, secrets_resolver_alice_with_new_did 44 | ): 45 | return ResolversConfig( 46 | secrets_resolver=secrets_resolver_alice_with_new_did, 47 | did_resolver=resolvers_config_alice.did_resolver, 48 | ) 49 | -------------------------------------------------------------------------------- /tests/unit/pack_encrypted/test_message_to_multiple_recipients.py: -------------------------------------------------------------------------------- 1 | from dataclasses import replace 2 | 3 | import pytest 4 | 5 | from didcomm.core.defaults import DEF_ENC_ALG_AUTH 6 | from didcomm.message import Message 7 | from didcomm.pack_encrypted import PackEncryptedConfig, pack_encrypted, ServiceMetadata 8 | from didcomm.protocols.routing.forward import unpack_forward 9 | from didcomm.unpack import unpack 10 | from tests.test_vectors.common import ALICE_DID, BOB_DID, CHARLIE_DID 11 | from tests.test_vectors.did_doc import ( 12 | DID_DOC_CHARLIE, 13 | DID_DOC_MEDIATOR2, 14 | ) 15 | 16 | 17 | @pytest.mark.asyncio 18 | async def test_message_to_multiple_recipients( 19 | resolvers_config_alice, 20 | resolvers_config_bob, 21 | resolvers_config_charlie, 22 | resolvers_config_mediator1, 23 | resolvers_config_mediator2, 24 | ): 25 | # ALICE 26 | message = Message( 27 | body={"aaa": 1, "bbb": 2}, 28 | id="1234567890", 29 | type="my-protocol/1.0", 30 | frm=ALICE_DID, 31 | to=[BOB_DID, CHARLIE_DID], 32 | ) 33 | 34 | pack_config = PackEncryptedConfig() 35 | 36 | pack_result_for_bob = await pack_encrypted( 37 | resolvers_config=resolvers_config_alice, 38 | message=message, 39 | frm=ALICE_DID, 40 | to=BOB_DID, 41 | pack_config=pack_config, 42 | ) 43 | packed_msg_for_bob = pack_result_for_bob.packed_msg 44 | 45 | pack_result_for_charlie = await pack_encrypted( 46 | resolvers_config=resolvers_config_alice, 47 | message=message, 48 | frm=ALICE_DID, 49 | to=CHARLIE_DID, 50 | pack_config=pack_config, 51 | ) 52 | packed_msg_for_charlie = pack_result_for_charlie.packed_msg 53 | 54 | assert pack_result_for_charlie.service_metadata == ServiceMetadata( 55 | id=DID_DOC_CHARLIE.service[0].id, 56 | service_endpoint=DID_DOC_MEDIATOR2.service[0].service_endpoint, 57 | ) 58 | 59 | # BOB's MEDIATOR 60 | forward_bob = await unpack_forward( 61 | resolvers_config_mediator1, packed_msg_for_bob, True 62 | ) 63 | 64 | # BOB 65 | unpack_result_at_bob = await unpack(resolvers_config_bob, forward_bob.forwarded_msg) 66 | 67 | # CHARLIE's MEDIATOR 68 | forward_forward_charlie = await unpack_forward( 69 | resolvers_config_mediator2, packed_msg_for_charlie, True 70 | ) 71 | 72 | # MEDIATOR2's MEDIATOR 73 | forward_charlie = await unpack_forward( 74 | resolvers_config_mediator1, forward_forward_charlie.forwarded_msg, True 75 | ) 76 | 77 | # CHARLIE 78 | unpack_result_at_charlie = await unpack( 79 | resolvers_config_charlie, forward_charlie.forwarded_msg 80 | ) 81 | 82 | assert unpack_result_at_bob.message == message 83 | 84 | assert unpack_result_at_bob.metadata.encrypted 85 | assert unpack_result_at_bob.metadata.authenticated 86 | assert not unpack_result_at_bob.metadata.non_repudiation 87 | assert not unpack_result_at_bob.metadata.anonymous_sender 88 | assert unpack_result_at_bob.metadata.enc_alg_auth == DEF_ENC_ALG_AUTH 89 | assert unpack_result_at_bob.metadata.enc_alg_anon is None 90 | 91 | assert unpack_result_at_charlie.message == message 92 | 93 | assert unpack_result_at_charlie.metadata.encrypted 94 | assert unpack_result_at_charlie.metadata.authenticated 95 | assert not unpack_result_at_charlie.metadata.non_repudiation 96 | assert not unpack_result_at_charlie.metadata.anonymous_sender 97 | assert unpack_result_at_charlie.metadata.enc_alg_auth == DEF_ENC_ALG_AUTH 98 | assert unpack_result_at_charlie.metadata.enc_alg_anon is None 99 | 100 | assert unpack_result_at_bob.message == unpack_result_at_charlie.message 101 | assert replace(unpack_result_at_bob.metadata, encrypted_to=None) == replace( 102 | unpack_result_at_charlie.metadata, encrypted_to=None 103 | ) 104 | -------------------------------------------------------------------------------- /tests/unit/pack_encrypted/test_pack_anon_encrypted.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.common.algorithms import AnonCryptAlg 4 | from didcomm.pack_encrypted import pack_encrypted, PackEncryptedConfig 5 | from didcomm.unpack import unpack 6 | from didcomm.protocols.routing.forward import unpack_forward 7 | from tests.test_vectors.common import BOB_DID, ALICE_DID 8 | from tests.test_vectors.didcomm_messages.messages import ( 9 | TEST_MESSAGE, 10 | attachment_json_msg, 11 | attachment_multi_1_msg, 12 | minimal_msg, 13 | ) 14 | from tests.test_vectors.utils import ( 15 | get_key_agreement_methods, 16 | Person, 17 | KeyAgreementCurveType, 18 | get_key_agreement_methods_in_secrets, 19 | get_auth_methods_in_secrets, 20 | ) 21 | 22 | 23 | @pytest.mark.asyncio 24 | @pytest.mark.parametrize( 25 | "msg", 26 | [TEST_MESSAGE, minimal_msg(), attachment_multi_1_msg(), attachment_json_msg()], 27 | ) 28 | @pytest.mark.parametrize( 29 | "alg", 30 | [ 31 | None, 32 | AnonCryptAlg.XC20P_ECDH_ES_A256KW, 33 | AnonCryptAlg.A256GCM_ECDH_ES_A256KW, 34 | AnonCryptAlg.A256CBC_HS512_ECDH_ES_A256KW, 35 | ], 36 | ) 37 | @pytest.mark.parametrize( 38 | "to", [BOB_DID] + [vm.id for vm in get_key_agreement_methods_in_secrets(Person.BOB)] 39 | ) 40 | @pytest.mark.parametrize( 41 | "sign_frm", 42 | [None, ALICE_DID] + [vm.id for vm in get_auth_methods_in_secrets(Person.ALICE)], 43 | ) 44 | @pytest.mark.parametrize("protect_sender_id", [True, False]) 45 | async def test_anoncrypt( 46 | msg, 47 | alg, 48 | to, 49 | sign_frm, 50 | protect_sender_id, 51 | resolvers_config_alice, 52 | resolvers_config_bob, 53 | resolvers_config_mediator1, 54 | ): 55 | pack_config = PackEncryptedConfig(protect_sender_id=protect_sender_id) 56 | if alg: 57 | pack_config.enc_alg_anon = alg 58 | pack_result = await pack_encrypted( 59 | resolvers_config=resolvers_config_alice, 60 | message=msg, 61 | to=to, 62 | sign_frm=sign_frm, 63 | pack_config=pack_config, 64 | ) 65 | 66 | expected_to = [to] 67 | if to == BOB_DID: 68 | expected_to = [ 69 | vm.id 70 | for vm in get_key_agreement_methods( 71 | Person.BOB, KeyAgreementCurveType.X25519 72 | ) 73 | ] 74 | expected_sign_frm = None 75 | if sign_frm is not None and sign_frm != ALICE_DID: 76 | expected_sign_frm = sign_frm 77 | if sign_frm == ALICE_DID: 78 | expected_sign_frm = get_auth_methods_in_secrets(Person.ALICE)[0].id 79 | 80 | assert pack_result.from_kid is None 81 | assert pack_result.to_kids == expected_to 82 | assert pack_result.sign_from_kid == expected_sign_frm 83 | assert pack_result.packed_msg is not None 84 | 85 | forward_bob = await unpack_forward( 86 | resolvers_config_mediator1, pack_result.packed_msg, True 87 | ) 88 | # TODO ??? might need some checks against forward unpack result 89 | # (but it's actually out of current test case scope) 90 | 91 | unpack_res = await unpack( 92 | resolvers_config=resolvers_config_bob, packed_msg=forward_bob.forwarded_msg 93 | ) 94 | expected_alg = alg or AnonCryptAlg.XC20P_ECDH_ES_A256KW 95 | assert unpack_res.message == msg 96 | assert unpack_res.metadata.enc_alg_anon == expected_alg 97 | assert unpack_res.metadata.enc_alg_auth is None 98 | assert unpack_res.metadata.anonymous_sender 99 | assert unpack_res.metadata.encrypted 100 | assert unpack_res.metadata.non_repudiation == (sign_frm is not None) 101 | assert unpack_res.metadata.authenticated == (sign_frm is not None) 102 | assert not unpack_res.metadata.re_wrapped_in_forward 103 | -------------------------------------------------------------------------------- /tests/unit/pack_encrypted/test_pack_encrypted_helpers.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pytest 4 | import attr 5 | from typing import List 6 | 7 | from didcomm.common.types import JSON_OBJ, DID_OR_DID_URL 8 | from didcomm import ( 9 | DIDDoc, 10 | DIDCommService, 11 | ResolversConfig, 12 | AnonCryptAlg, 13 | PackEncryptedConfig, 14 | PackEncryptedParameters, 15 | ) 16 | 17 | from didcomm.pack_encrypted import __forward_if_needed 18 | 19 | from tests import mock_module 20 | from tests.unit.conftest import build_didcomm_service 21 | 22 | 23 | @pytest.fixture 24 | def did_doc(did) -> DIDDoc: 25 | return DIDDoc(did, [], [], [], []) 26 | 27 | 28 | @pytest.fixture 29 | def wrap_in_forward_mock(mocker) -> mock_module.AsyncMock: 30 | return mocker.patch.object(sys.modules["didcomm.pack_encrypted"], "wrap_in_forward") 31 | 32 | 33 | @attr.s(auto_attribs=True) 34 | class _TestData: 35 | resolvers_config: ResolversConfig 36 | packed_msg: JSON_OBJ 37 | to: DID_OR_DID_URL 38 | did_services_chain: List[DIDCommService] 39 | pack_config: PackEncryptedConfig 40 | pack_params: PackEncryptedParameters 41 | 42 | 43 | @pytest.fixture 44 | def test_data(resolvers_config_mock, didcomm_service, pack_config, pack_params): 45 | msg = "somemsg" 46 | to = "did:example:some_to" 47 | 48 | def id_gen_func(): 49 | return "123" 50 | 51 | pack_config.enc_alg_anon = AnonCryptAlg.XC20P_ECDH_ES_A256KW 52 | pack_params.forward_headers = ({"some": "header"},) 53 | pack_params.forward_didcomm_id_generator = id_gen_func 54 | 55 | return _TestData( 56 | resolvers_config_mock, msg, to, [didcomm_service], pack_config, pack_params 57 | ) 58 | 59 | 60 | # =================== 61 | # __forward_if_needed 62 | # =================== 63 | 64 | 65 | @pytest.mark.asyncio 66 | async def test_forward_if_needed__forward_off(wrap_in_forward_mock, test_data): 67 | test_data.pack_config.forward = False 68 | res = await __forward_if_needed(**attr.asdict(test_data, recurse=False)) 69 | assert res is None 70 | 71 | 72 | @pytest.mark.asyncio 73 | async def test_forward_if_needed__no_did_services(wrap_in_forward_mock, test_data): 74 | test_data.did_services_chain = [] 75 | res = await __forward_if_needed(**attr.asdict(test_data, recurse=False)) 76 | assert res is None 77 | 78 | 79 | @pytest.mark.asyncio 80 | async def test_forward_if_needed__no_routing_keys(wrap_in_forward_mock, test_data): 81 | src = test_data.did_services_chain[-1] 82 | test_data.did_services_chain[-1] = build_didcomm_service( 83 | id=src.id, 84 | service_endpoint=src.service_endpoint, 85 | routing_keys=[], # no routing keys 86 | recipient_keys=src.recipient_keys, 87 | accept=src.accept, 88 | ) 89 | 90 | res = await __forward_if_needed(**attr.asdict(test_data, recurse=False)) 91 | assert res is None 92 | 93 | 94 | @pytest.mark.asyncio 95 | async def test_forward_if_needed__single_service(wrap_in_forward_mock, test_data): 96 | await __forward_if_needed(**attr.asdict(test_data, recurse=False)) 97 | 98 | wrap_in_forward_mock.assert_awaited_once_with( 99 | resolvers_config=test_data.resolvers_config, 100 | packed_msg=test_data.packed_msg, 101 | to=test_data.to, 102 | routing_keys=test_data.did_services_chain[0].routing_keys, 103 | enc_alg_anon=test_data.pack_config.enc_alg_anon, 104 | headers=test_data.pack_params.forward_headers, 105 | didcomm_id_generator=test_data.pack_params.forward_didcomm_id_generator, 106 | ) 107 | 108 | 109 | @pytest.mark.asyncio 110 | async def test_forward_if_needed__multiple_services( 111 | didcomm_service1, 112 | didcomm_service2, 113 | didcomm_service3, 114 | wrap_in_forward_mock, 115 | test_data, 116 | ): 117 | test_data.did_services_chain = [ 118 | didcomm_service2, 119 | didcomm_service3, 120 | didcomm_service1, 121 | ] 122 | exp_routing_keys = [ 123 | didcomm_service3.service_endpoint, 124 | didcomm_service1.service_endpoint, 125 | ] + didcomm_service1.routing_keys 126 | 127 | await __forward_if_needed(**attr.asdict(test_data, recurse=False)) 128 | 129 | wrap_in_forward_mock.assert_awaited_once_with( 130 | resolvers_config=test_data.resolvers_config, 131 | packed_msg=test_data.packed_msg, 132 | to=test_data.to, 133 | routing_keys=exp_routing_keys, 134 | enc_alg_anon=test_data.pack_config.enc_alg_anon, 135 | headers=test_data.pack_params.forward_headers, 136 | didcomm_id_generator=test_data.pack_params.forward_didcomm_id_generator, 137 | ) 138 | -------------------------------------------------------------------------------- /tests/unit/pack_encrypted/test_pack_encrypted_with_from_prior.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.core.utils import is_did_with_uri_fragment, get_did 4 | from didcomm.pack_encrypted import ( 5 | pack_encrypted, 6 | PackEncryptedParameters, 7 | PackEncryptedConfig, 8 | ) 9 | from didcomm.unpack import unpack 10 | from tests.test_vectors.common import ALICE_DID, BOB_DID, CHARLIE_DID 11 | from tests.test_vectors.didcomm_messages.messages import ( 12 | TEST_MESSAGE_FROM_PRIOR_MINIMAL, 13 | TEST_MESSAGE_FROM_PRIOR, 14 | ) 15 | from tests.test_vectors.secrets.mock_secrets_resolver_charlie import ( 16 | CHARLIE_SECRET_AUTH_KEY_ED25519, 17 | ) 18 | 19 | 20 | @pytest.mark.parametrize( 21 | "message", [TEST_MESSAGE_FROM_PRIOR_MINIMAL, TEST_MESSAGE_FROM_PRIOR] 22 | ) 23 | @pytest.mark.asyncio 24 | async def test_pack_encrypted_with_from_prior_and_issuer_kid( 25 | message, 26 | resolvers_config_charlie_rotated_to_alice, 27 | resolvers_config_bob, 28 | ): 29 | pack_result = await pack_encrypted( 30 | resolvers_config=resolvers_config_charlie_rotated_to_alice, 31 | message=message, 32 | to=BOB_DID, 33 | frm=ALICE_DID, 34 | pack_config=PackEncryptedConfig(forward=False), 35 | pack_params=PackEncryptedParameters( 36 | from_prior_issuer_kid=CHARLIE_SECRET_AUTH_KEY_ED25519.kid 37 | ), 38 | ) 39 | unpack_result = await unpack(resolvers_config_bob, pack_result.packed_msg) 40 | 41 | assert unpack_result.message == message 42 | assert ( 43 | unpack_result.metadata.from_prior_issuer_kid 44 | == CHARLIE_SECRET_AUTH_KEY_ED25519.kid 45 | ) 46 | assert unpack_result.metadata.from_prior_jwt is not None 47 | 48 | 49 | @pytest.mark.parametrize( 50 | "message", [TEST_MESSAGE_FROM_PRIOR_MINIMAL, TEST_MESSAGE_FROM_PRIOR] 51 | ) 52 | @pytest.mark.asyncio 53 | async def test_pack_encrypted_with_from_prior_and_no_issuer_kid( 54 | message, 55 | resolvers_config_charlie_rotated_to_alice, 56 | resolvers_config_bob, 57 | ): 58 | pack_result = await pack_encrypted( 59 | resolvers_config=resolvers_config_charlie_rotated_to_alice, 60 | message=message, 61 | to=BOB_DID, 62 | frm=ALICE_DID, 63 | pack_config=PackEncryptedConfig(forward=False), 64 | ) 65 | unpack_result = await unpack(resolvers_config_bob, pack_result.packed_msg) 66 | 67 | assert unpack_result.message == message 68 | assert is_did_with_uri_fragment(unpack_result.metadata.from_prior_issuer_kid) 69 | assert get_did(unpack_result.metadata.from_prior_issuer_kid) == CHARLIE_DID 70 | assert unpack_result.metadata.from_prior_jwt is not None 71 | -------------------------------------------------------------------------------- /tests/unit/pack_encrypted/test_pack_encrypted_with_from_prior_negative.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.pack_encrypted import pack_encrypted, PackEncryptedConfig 4 | from tests.test_vectors.common import ( 5 | BOB_DID, 6 | ALICE_DID, 7 | ) 8 | from tests.test_vectors.didcomm_messages.messages import ( 9 | INVALID_FROM_PRIOR_TEST_VECTORS, 10 | ) 11 | 12 | 13 | @pytest.mark.parametrize("test_vector", INVALID_FROM_PRIOR_TEST_VECTORS) 14 | @pytest.mark.asyncio 15 | async def test_pack_encrypted_with_invalid_from_prior( 16 | test_vector, 17 | resolvers_config_charlie_rotated_to_alice, 18 | resolvers_config_bob, 19 | ): 20 | with pytest.raises(test_vector.exc): 21 | await pack_encrypted( 22 | resolvers_config=resolvers_config_charlie_rotated_to_alice, 23 | message=test_vector.value, 24 | to=BOB_DID, 25 | frm=ALICE_DID, 26 | pack_config=PackEncryptedConfig(forward=False), 27 | ) 28 | -------------------------------------------------------------------------------- /tests/unit/pack_plaintext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/unit/pack_plaintext/__init__.py -------------------------------------------------------------------------------- /tests/unit/pack_plaintext/common.py: -------------------------------------------------------------------------------- 1 | from didcomm.message import ( 2 | Message, 3 | ) 4 | 5 | 6 | def create_minimal_msg(): 7 | return Message( 8 | id="1234567890", 9 | type="http://example.com/protocols/lets_do_lunch/1.0/proposal", 10 | body={}, 11 | ) 12 | -------------------------------------------------------------------------------- /tests/unit/pack_plaintext/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture() 5 | def resolvers_config_alice(resolvers_config_alice_with_non_secrets): 6 | return resolvers_config_alice_with_non_secrets 7 | 8 | 9 | @pytest.fixture() 10 | def resolvers_config_bob(resolvers_config_bob_with_non_secrets): 11 | return resolvers_config_bob_with_non_secrets 12 | 13 | 14 | @pytest.fixture() 15 | def did_resolver(did_resolver_with_non_secrets): 16 | return did_resolver_with_non_secrets 17 | -------------------------------------------------------------------------------- /tests/unit/pack_plaintext/test_pack_plaintext.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.core.serialization import json_str_to_dict 4 | from didcomm.pack_plaintext import pack_plaintext 5 | from didcomm.unpack import unpack 6 | from tests.test_vectors.didcomm_messages.messages import ( 7 | TEST_MESSAGE, 8 | attachment_json_msg, 9 | attachment_links_msg, 10 | attachment_base64_msg, 11 | minimal_msg, 12 | attachment_multi_1_msg, 13 | attachment_multi_2_msg, 14 | ack_msg, 15 | custom_headers_msg, 16 | ) 17 | from tests.test_vectors.didcomm_messages.spec.spec_test_vectors_plaintext import ( 18 | TEST_PLAINTEXT_DIDCOMM_MESSAGE_SIMPLE, 19 | ) 20 | from tests.test_vectors.didcomm_messages.tests.test_vectors_plaintext_positive import ( 21 | TEST_PLAINTEXT_ATTACHMENT_BASE64, 22 | TEST_PLAINTEXT_ATTACHMENT_LINKS, 23 | TEST_PLAINTEXT_ATTACHMENT_JSON, 24 | TEST_PLAINTEXT_ATTACHMENT_MULTI_1, 25 | TEST_PLAINTEXT_ATTACHMENT_MULTI_2, 26 | TEST_PLAINTEXT_DIDCOMM_MESSAGE_MINIMAL, 27 | TEST_PLAINTEXT_ACKS, 28 | TEST_PLAINTEXT_DIDCOMM_MESSAGE_WITH_CUSTOM_HEADERS, 29 | ) 30 | 31 | 32 | async def check_pack_plaintext(message, expected_json, resolvers_config_bob): 33 | pack_result = await pack_plaintext(resolvers_config_bob, message) 34 | assert json_str_to_dict(pack_result.packed_msg) == json_str_to_dict(expected_json) 35 | 36 | unpack_result = await unpack(resolvers_config_bob, pack_result.packed_msg) 37 | assert unpack_result.message == message 38 | 39 | 40 | @pytest.mark.asyncio 41 | async def test_pack_simple_plaintext(resolvers_config_bob): 42 | await check_pack_plaintext( 43 | TEST_MESSAGE, TEST_PLAINTEXT_DIDCOMM_MESSAGE_SIMPLE, resolvers_config_bob 44 | ) 45 | 46 | 47 | @pytest.mark.asyncio 48 | async def test_pack_plaintext_with_custom_headers(resolvers_config_bob): 49 | await check_pack_plaintext( 50 | custom_headers_msg(), 51 | TEST_PLAINTEXT_DIDCOMM_MESSAGE_WITH_CUSTOM_HEADERS, 52 | resolvers_config_bob, 53 | ) 54 | 55 | 56 | @pytest.mark.asyncio 57 | async def test_pack_minimal_plaintext(resolvers_config_bob): 58 | await check_pack_plaintext( 59 | minimal_msg(), TEST_PLAINTEXT_DIDCOMM_MESSAGE_MINIMAL, resolvers_config_bob 60 | ) 61 | 62 | 63 | @pytest.mark.asyncio 64 | async def test_pack_attachments_base64(resolvers_config_bob): 65 | await check_pack_plaintext( 66 | attachment_base64_msg(), TEST_PLAINTEXT_ATTACHMENT_BASE64, resolvers_config_bob 67 | ) 68 | 69 | 70 | @pytest.mark.asyncio 71 | async def test_pack_attachments_links(resolvers_config_bob): 72 | await check_pack_plaintext( 73 | attachment_links_msg(), TEST_PLAINTEXT_ATTACHMENT_LINKS, resolvers_config_bob 74 | ) 75 | 76 | 77 | @pytest.mark.asyncio 78 | async def test_pack_attachments_json(resolvers_config_bob): 79 | await check_pack_plaintext( 80 | attachment_json_msg(), TEST_PLAINTEXT_ATTACHMENT_JSON, resolvers_config_bob 81 | ) 82 | 83 | 84 | @pytest.mark.asyncio 85 | async def test_pack_attachments_multi1(resolvers_config_bob): 86 | await check_pack_plaintext( 87 | attachment_multi_1_msg(), 88 | TEST_PLAINTEXT_ATTACHMENT_MULTI_1, 89 | resolvers_config_bob, 90 | ) 91 | 92 | 93 | @pytest.mark.asyncio 94 | async def test_pack_attachments_multi2(resolvers_config_bob): 95 | await check_pack_plaintext( 96 | attachment_multi_2_msg(), 97 | TEST_PLAINTEXT_ATTACHMENT_MULTI_2, 98 | resolvers_config_bob, 99 | ) 100 | 101 | 102 | @pytest.mark.asyncio 103 | async def test_pack_acks(resolvers_config_bob): 104 | await check_pack_plaintext( 105 | ack_msg(), 106 | TEST_PLAINTEXT_ACKS, 107 | resolvers_config_bob, 108 | ) 109 | -------------------------------------------------------------------------------- /tests/unit/pack_plaintext/test_pack_plaintext_with_from_prior.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.core.serialization import json_str_to_dict 4 | from didcomm.core.utils import get_did, is_did_with_uri_fragment 5 | from didcomm.pack_plaintext import pack_plaintext, PackPlaintextParameters 6 | from didcomm.unpack import unpack 7 | from tests.test_vectors.common import ( 8 | CHARLIE_DID, 9 | ) 10 | from tests.test_vectors.didcomm_messages.messages import ( 11 | TEST_MESSAGE_FROM_PRIOR_MINIMAL, 12 | TEST_MESSAGE_FROM_PRIOR, 13 | ) 14 | from tests.test_vectors.secrets.mock_secrets_resolver_charlie import ( 15 | CHARLIE_SECRET_AUTH_KEY_ED25519, 16 | ) 17 | 18 | 19 | @pytest.mark.parametrize( 20 | "message", [TEST_MESSAGE_FROM_PRIOR_MINIMAL, TEST_MESSAGE_FROM_PRIOR] 21 | ) 22 | @pytest.mark.asyncio 23 | async def test_pack_plaintext_with_from_prior_and_issuer_kid( 24 | message, 25 | resolvers_config_charlie_rotated_to_alice, 26 | resolvers_config_bob, 27 | ): 28 | pack_result = await pack_plaintext( 29 | resolvers_config=resolvers_config_charlie_rotated_to_alice, 30 | message=message, 31 | pack_params=PackPlaintextParameters( 32 | from_prior_issuer_kid=CHARLIE_SECRET_AUTH_KEY_ED25519.kid 33 | ), 34 | ) 35 | unpack_result = await unpack(resolvers_config_bob, pack_result.packed_msg) 36 | 37 | assert unpack_result.message == message 38 | assert ( 39 | unpack_result.metadata.from_prior_issuer_kid 40 | == CHARLIE_SECRET_AUTH_KEY_ED25519.kid 41 | ) 42 | assert ( 43 | unpack_result.metadata.from_prior_jwt 44 | == json_str_to_dict(pack_result.packed_msg)["from_prior"] 45 | ) 46 | 47 | 48 | @pytest.mark.parametrize( 49 | "message", [TEST_MESSAGE_FROM_PRIOR_MINIMAL, TEST_MESSAGE_FROM_PRIOR] 50 | ) 51 | @pytest.mark.asyncio 52 | async def test_pack_plaintext_with_from_prior_and_no_issuer_kid( 53 | message, 54 | resolvers_config_charlie_rotated_to_alice, 55 | resolvers_config_bob, 56 | ): 57 | pack_result = await pack_plaintext( 58 | resolvers_config=resolvers_config_charlie_rotated_to_alice, 59 | message=message, 60 | ) 61 | unpack_result = await unpack(resolvers_config_bob, pack_result.packed_msg) 62 | 63 | assert unpack_result.message == message 64 | assert is_did_with_uri_fragment(unpack_result.metadata.from_prior_issuer_kid) 65 | assert get_did(unpack_result.metadata.from_prior_issuer_kid) == CHARLIE_DID 66 | assert ( 67 | unpack_result.metadata.from_prior_jwt 68 | == json_str_to_dict(pack_result.packed_msg)["from_prior"] 69 | ) 70 | -------------------------------------------------------------------------------- /tests/unit/pack_plaintext/test_pack_plaintext_with_from_prior_negative.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.pack_plaintext import pack_plaintext 4 | from tests.test_vectors.didcomm_messages.messages import ( 5 | INVALID_FROM_PRIOR_TEST_VECTORS, 6 | ) 7 | 8 | 9 | @pytest.mark.parametrize("test_vector", INVALID_FROM_PRIOR_TEST_VECTORS) 10 | @pytest.mark.asyncio 11 | async def test_pack_plaintext_with_invalid_from_prior( 12 | test_vector, 13 | resolvers_config_charlie_rotated_to_alice, 14 | resolvers_config_bob, 15 | ): 16 | with pytest.raises(test_vector.exc): 17 | await pack_plaintext( 18 | resolvers_config=resolvers_config_charlie_rotated_to_alice, 19 | message=test_vector.value, 20 | ) 21 | -------------------------------------------------------------------------------- /tests/unit/pack_signed/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/unit/pack_signed/__init__.py -------------------------------------------------------------------------------- /tests/unit/pack_signed/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture() 5 | def resolvers_config_alice(resolvers_config_alice_with_non_secrets): 6 | return resolvers_config_alice_with_non_secrets 7 | 8 | 9 | @pytest.fixture() 10 | def resolvers_config_bob(resolvers_config_bob_with_non_secrets): 11 | return resolvers_config_bob_with_non_secrets 12 | 13 | 14 | @pytest.fixture() 15 | def did_resolver(did_resolver_with_non_secrets): 16 | return did_resolver_with_non_secrets 17 | -------------------------------------------------------------------------------- /tests/unit/pack_signed/test_pack_signed.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.pack_signed import pack_signed 4 | from didcomm.unpack import unpack 5 | from tests.test_vectors.common import ALICE_DID 6 | from tests.test_vectors.didcomm_messages.messages import ( 7 | TEST_MESSAGE, 8 | minimal_msg, 9 | attachment_multi_1_msg, 10 | attachment_json_msg, 11 | ) 12 | from tests.test_vectors.utils import get_auth_methods_in_secrets, Person 13 | 14 | 15 | @pytest.mark.asyncio 16 | @pytest.mark.parametrize( 17 | "msg", 18 | [TEST_MESSAGE, minimal_msg(), attachment_multi_1_msg(), attachment_json_msg()], 19 | ) 20 | @pytest.mark.parametrize( 21 | "sign_frm", 22 | [ALICE_DID] + [vm.id for vm in get_auth_methods_in_secrets(Person.ALICE)], 23 | ) 24 | async def test_anoncrypt(msg, sign_frm, resolvers_config_alice, resolvers_config_bob): 25 | pack_result = await pack_signed( 26 | resolvers_config=resolvers_config_alice, message=msg, sign_frm=sign_frm 27 | ) 28 | 29 | expected_sign_frm = get_auth_methods_in_secrets(Person.ALICE)[0].id 30 | if sign_frm != ALICE_DID: 31 | expected_sign_frm = sign_frm 32 | 33 | assert pack_result.sign_from_kid == expected_sign_frm 34 | assert pack_result.packed_msg is not None 35 | 36 | unpack_res = await unpack( 37 | resolvers_config=resolvers_config_bob, packed_msg=pack_result.packed_msg 38 | ) 39 | assert unpack_res.message == msg 40 | assert unpack_res.metadata.non_repudiation 41 | assert unpack_res.metadata.authenticated 42 | assert unpack_res.metadata.enc_alg_anon is None 43 | assert unpack_res.metadata.enc_alg_auth is None 44 | assert not unpack_res.metadata.anonymous_sender 45 | assert not unpack_res.metadata.encrypted 46 | assert not unpack_res.metadata.re_wrapped_in_forward 47 | -------------------------------------------------------------------------------- /tests/unit/pack_signed/test_pack_signed_negative.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.errors import ( 4 | DIDCommValueError, 5 | DIDDocNotResolvedError, 6 | SecretNotFoundError, 7 | ) 8 | from didcomm.pack_signed import pack_signed 9 | from tests.test_vectors.common import ALICE_DID 10 | from tests.test_vectors.didcomm_messages.messages import TEST_MESSAGE 11 | from tests.test_vectors.utils import get_auth_methods_not_in_secrets, Person 12 | 13 | 14 | @pytest.mark.asyncio 15 | async def test_from_is_not_a_did_or_did_url(resolvers_config_alice): 16 | with pytest.raises(DIDCommValueError): 17 | await pack_signed( 18 | resolvers_config=resolvers_config_alice, 19 | message=TEST_MESSAGE, 20 | sign_frm="not-a-did", 21 | ) 22 | 23 | 24 | @pytest.mark.asyncio 25 | async def test_from_unknown_did(resolvers_config_alice): 26 | with pytest.raises(DIDDocNotResolvedError): 27 | await pack_signed( 28 | resolvers_config=resolvers_config_alice, 29 | message=TEST_MESSAGE, 30 | sign_frm="did:example:unknown", 31 | ) 32 | 33 | 34 | @pytest.mark.asyncio 35 | async def test_from_unknown_did_url(resolvers_config_alice): 36 | with pytest.raises(SecretNotFoundError): 37 | await pack_signed( 38 | resolvers_config=resolvers_config_alice, 39 | message=TEST_MESSAGE, 40 | sign_frm=ALICE_DID + "#unknown-key", 41 | ) 42 | 43 | 44 | @pytest.mark.asyncio 45 | async def test_from_not_in_secrets(resolvers_config_alice): 46 | frm = get_auth_methods_not_in_secrets(Person.ALICE)[0].id 47 | with pytest.raises(SecretNotFoundError): 48 | await pack_signed( 49 | resolvers_config=resolvers_config_alice, message=TEST_MESSAGE, sign_frm=frm 50 | ) 51 | -------------------------------------------------------------------------------- /tests/unit/pack_signed/test_pack_signed_with_from_prior.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.core.utils import is_did_with_uri_fragment, get_did 4 | from didcomm.pack_signed import pack_signed, PackSignedParameters 5 | from didcomm.unpack import unpack 6 | from tests.test_vectors.common import ALICE_DID, CHARLIE_DID 7 | from tests.test_vectors.didcomm_messages.messages import ( 8 | TEST_MESSAGE_FROM_PRIOR_MINIMAL, 9 | TEST_MESSAGE_FROM_PRIOR, 10 | ) 11 | from tests.test_vectors.secrets.mock_secrets_resolver_charlie import ( 12 | CHARLIE_SECRET_AUTH_KEY_ED25519, 13 | ) 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "message", [TEST_MESSAGE_FROM_PRIOR_MINIMAL, TEST_MESSAGE_FROM_PRIOR] 18 | ) 19 | @pytest.mark.asyncio 20 | async def test_pack_plaintext_with_from_prior_and_issuer_kid( 21 | message, 22 | resolvers_config_charlie_rotated_to_alice, 23 | resolvers_config_bob, 24 | ): 25 | pack_result = await pack_signed( 26 | resolvers_config=resolvers_config_charlie_rotated_to_alice, 27 | message=message, 28 | sign_frm=ALICE_DID, 29 | pack_params=PackSignedParameters( 30 | from_prior_issuer_kid=CHARLIE_SECRET_AUTH_KEY_ED25519.kid 31 | ), 32 | ) 33 | unpack_result = await unpack(resolvers_config_bob, pack_result.packed_msg) 34 | 35 | assert unpack_result.message == message 36 | assert ( 37 | unpack_result.metadata.from_prior_issuer_kid 38 | == CHARLIE_SECRET_AUTH_KEY_ED25519.kid 39 | ) 40 | assert unpack_result.metadata.from_prior_jwt is not None 41 | 42 | 43 | @pytest.mark.parametrize( 44 | "message", [TEST_MESSAGE_FROM_PRIOR_MINIMAL, TEST_MESSAGE_FROM_PRIOR] 45 | ) 46 | @pytest.mark.asyncio 47 | async def test_pack_plaintext_with_from_prior_and_no_issuer_kid( 48 | message, 49 | resolvers_config_charlie_rotated_to_alice, 50 | resolvers_config_bob, 51 | ): 52 | pack_result = await pack_signed( 53 | resolvers_config=resolvers_config_charlie_rotated_to_alice, 54 | message=message, 55 | sign_frm=ALICE_DID, 56 | ) 57 | unpack_result = await unpack(resolvers_config_bob, pack_result.packed_msg) 58 | 59 | assert unpack_result.message == message 60 | assert is_did_with_uri_fragment(unpack_result.metadata.from_prior_issuer_kid) 61 | assert get_did(unpack_result.metadata.from_prior_issuer_kid) == CHARLIE_DID 62 | assert unpack_result.metadata.from_prior_jwt is not None 63 | -------------------------------------------------------------------------------- /tests/unit/pack_signed/test_pack_signed_with_from_prior_negative.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.pack_signed import pack_signed 4 | from tests.test_vectors.common import ( 5 | ALICE_DID, 6 | ) 7 | from tests.test_vectors.didcomm_messages.messages import ( 8 | INVALID_FROM_PRIOR_TEST_VECTORS, 9 | ) 10 | 11 | 12 | @pytest.mark.parametrize("test_vector", INVALID_FROM_PRIOR_TEST_VECTORS) 13 | @pytest.mark.asyncio 14 | async def test_pack_signed_with_invalid_from_prior( 15 | test_vector, 16 | resolvers_config_charlie_rotated_to_alice, 17 | resolvers_config_bob, 18 | ): 19 | with pytest.raises(test_vector.exc): 20 | await pack_signed( 21 | resolvers_config=resolvers_config_charlie_rotated_to_alice, 22 | message=test_vector.value, 23 | sign_frm=ALICE_DID, 24 | ) 25 | -------------------------------------------------------------------------------- /tests/unit/secrets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/unit/secrets/__init__.py -------------------------------------------------------------------------------- /tests/unit/secrets/test_secrets_resolver_demo.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.secrets.secrets_resolver_demo import SecretsResolverDemo 4 | from didcomm.secrets.secrets_util import ( 5 | jwk_to_secret, 6 | generate_ed25519_keys_as_jwk_dict, 7 | ) 8 | 9 | 10 | @pytest.fixture() 11 | def secrets_resolver(tmp_path): 12 | tmp_file = tmp_path / "secrets.json" 13 | return SecretsResolverDemo(tmp_file) 14 | 15 | 16 | def create_secret(): 17 | return jwk_to_secret(generate_ed25519_keys_as_jwk_dict()[0]) 18 | 19 | 20 | @pytest.fixture() 21 | def secrets(): 22 | secret1 = create_secret() 23 | secret2 = create_secret() 24 | return secret1, secret2 25 | 26 | 27 | @pytest.mark.asyncio 28 | async def test_add_get_keys(secrets_resolver, secrets): 29 | secret1, secret2 = secrets 30 | 31 | await secrets_resolver.add_key(secret1) 32 | await secrets_resolver.add_key(secret2) 33 | 34 | assert await secrets_resolver.get_key(secret1.kid) == secret1 35 | assert await secrets_resolver.get_key(secret2.kid) == secret2 36 | assert await secrets_resolver.get_key("unknown-kid") is None 37 | assert await secrets_resolver.get_kids() == [secret1.kid, secret2.kid] 38 | assert await secrets_resolver.get_keys([secret1.kid, secret2.kid]) == [ 39 | secret1.kid, 40 | secret2.kid, 41 | ] 42 | assert await secrets_resolver.get_keys([secret1.kid]) == [secret1.kid] 43 | assert await secrets_resolver.get_keys([secret2.kid]) == [secret2.kid] 44 | assert await secrets_resolver.get_keys(["unknown-kid"]) == [] 45 | 46 | 47 | @pytest.mark.asyncio 48 | async def test_load_preserves_keys(secrets_resolver, secrets): 49 | secret1, secret2 = secrets 50 | 51 | await secrets_resolver.add_key(secret1) 52 | await secrets_resolver.add_key(secret2) 53 | 54 | secrets_resolver = SecretsResolverDemo(secrets_resolver.file_path) 55 | 56 | assert await secrets_resolver.get_key(secret1.kid) == secret1 57 | assert await secrets_resolver.get_key(secret2.kid) == secret2 58 | assert await secrets_resolver.get_key("unknown-kid") is None 59 | assert await secrets_resolver.get_kids() == [secret1.kid, secret2.kid] 60 | assert await secrets_resolver.get_keys([secret1.kid, secret2.kid]) == [ 61 | secret1.kid, 62 | secret2.kid, 63 | ] 64 | assert await secrets_resolver.get_keys([secret1.kid]) == [secret1.kid] 65 | assert await secrets_resolver.get_keys([secret2.kid]) == [secret2.kid] 66 | assert await secrets_resolver.get_keys(["unknown-kid"]) == [] 67 | -------------------------------------------------------------------------------- /tests/unit/secrets/test_secrets_util.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.common.types import VerificationMethodType, VerificationMaterialFormat 4 | from didcomm.secrets.secrets_util import ( 5 | generate_ed25519_keys_as_jwk_dict, 6 | generate_x25519_keys_as_jwk_dict, 7 | jwk_to_secret, 8 | secret_to_jwk_dict, 9 | ) 10 | 11 | 12 | def test_generate_ed25519_keys_as_jwk_dict(): 13 | private_key, public_key = generate_ed25519_keys_as_jwk_dict() 14 | assert private_key is not None 15 | assert public_key is not None 16 | assert isinstance(private_key, dict) 17 | assert isinstance(public_key, dict) 18 | assert private_key["crv"] == "Ed25519" 19 | assert public_key["crv"] == "Ed25519" 20 | assert private_key["kty"] == "OKP" 21 | assert public_key["kty"] == "OKP" 22 | assert "x" in private_key 23 | assert "d" in private_key 24 | assert "x" in public_key 25 | assert "d" not in public_key 26 | 27 | 28 | def test_generate_x25519_keys_as_jwk_dict(): 29 | private_key, public_key = generate_x25519_keys_as_jwk_dict() 30 | assert private_key is not None 31 | assert public_key is not None 32 | assert isinstance(private_key, dict) 33 | assert isinstance(public_key, dict) 34 | assert private_key["crv"] == "X25519" 35 | assert public_key["crv"] == "X25519" 36 | assert private_key["kty"] == "OKP" 37 | assert public_key["kty"] == "OKP" 38 | assert "x" in private_key 39 | assert "d" in private_key 40 | assert "x" in public_key 41 | assert "d" not in public_key 42 | 43 | 44 | @pytest.mark.parametrize( 45 | "private_key", 46 | [ 47 | pytest.param(generate_ed25519_keys_as_jwk_dict()[0], id="ed25519"), 48 | pytest.param(generate_x25519_keys_as_jwk_dict()[0], id="x25519"), 49 | ], 50 | ) 51 | def test_jwk_to_secret(private_key): 52 | secret = jwk_to_secret(private_key) 53 | assert secret.type == VerificationMethodType.JSON_WEB_KEY_2020 54 | assert secret.kid == private_key["kid"] 55 | assert secret.verification_material.format == VerificationMaterialFormat.JWK 56 | assert isinstance(secret.verification_material.value, str) # expect JSON 57 | 58 | 59 | @pytest.mark.parametrize( 60 | "private_key", 61 | [ 62 | pytest.param(generate_ed25519_keys_as_jwk_dict()[0], id="ed25519"), 63 | pytest.param(generate_x25519_keys_as_jwk_dict()[0], id="x25519"), 64 | ], 65 | ) 66 | def test_secret_to_jwk(private_key): 67 | secret = jwk_to_secret(private_key) 68 | jwk = secret_to_jwk_dict(secret) 69 | assert private_key == jwk 70 | 71 | 72 | @pytest.mark.parametrize( 73 | "private_key", 74 | [ 75 | pytest.param(generate_ed25519_keys_as_jwk_dict()[0], id="ed25519"), 76 | pytest.param(generate_x25519_keys_as_jwk_dict()[0], id="x25519"), 77 | ], 78 | ) 79 | def test_secret_to_jwk_updates_kid(private_key): 80 | secret = jwk_to_secret(private_key) 81 | secret.kid = "my-secret" 82 | jwk = secret_to_jwk_dict(secret) 83 | assert jwk["kid"] == "my-secret" 84 | del private_key["kid"] 85 | del jwk["kid"] 86 | assert private_key == jwk 87 | -------------------------------------------------------------------------------- /tests/unit/spec_test_vectors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/unit/spec_test_vectors/__init__.py -------------------------------------------------------------------------------- /tests/unit/spec_test_vectors/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture() 5 | def resolvers_config_alice(resolvers_config_alice_all_in_secrets): 6 | return resolvers_config_alice_all_in_secrets 7 | 8 | 9 | @pytest.fixture() 10 | def resolvers_config_bob(resolvers_config_bob_all_in_secrets): 11 | return resolvers_config_bob_all_in_secrets 12 | 13 | 14 | @pytest.fixture() 15 | def did_resolver(did_resolver_all_in_secrets): 16 | return did_resolver_all_in_secrets 17 | -------------------------------------------------------------------------------- /tests/unit/spec_test_vectors/test_pack_unpack_anon_crypt.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.pack_encrypted import pack_encrypted, PackEncryptedConfig 4 | from didcomm.unpack import unpack 5 | from tests.test_vectors.common import BOB_DID 6 | from tests.test_vectors.didcomm_messages.messages import TEST_MESSAGE 7 | from tests.test_vectors.didcomm_messages.spec.spec_test_vectors_anon_encrypted import ( 8 | TEST_ENCRYPTED_DIDCOMM_MESSAGE_ANON, 9 | ) 10 | from tests.unit.common import check_unpack_test_vector, decode_jwe_headers 11 | 12 | 13 | @pytest.mark.asyncio 14 | @pytest.mark.parametrize("test_vector", TEST_ENCRYPTED_DIDCOMM_MESSAGE_ANON) 15 | async def test_unpack_anoncrypt(test_vector, resolvers_config_bob): 16 | await check_unpack_test_vector(test_vector, resolvers_config_bob) 17 | 18 | 19 | @pytest.mark.asyncio 20 | async def test_pack_anoncrypt_recipient_as_did( 21 | resolvers_config_alice, resolvers_config_bob 22 | ): 23 | test_vector = TEST_ENCRYPTED_DIDCOMM_MESSAGE_ANON[0] 24 | expected_metadata = test_vector.metadata 25 | pack_result = await pack_encrypted( 26 | resolvers_config_alice, 27 | TEST_MESSAGE, 28 | to=BOB_DID, 29 | pack_config=PackEncryptedConfig( 30 | enc_alg_anon=expected_metadata.enc_alg_anon, forward=False 31 | ), 32 | ) 33 | pack_result_headers = decode_jwe_headers(pack_result.packed_msg) 34 | expected_headers = decode_jwe_headers(test_vector.value) 35 | assert pack_result_headers == expected_headers 36 | assert pack_result.to_kids == expected_metadata.encrypted_to 37 | assert pack_result.from_kid == expected_metadata.encrypted_from 38 | assert pack_result.sign_from_kid == expected_metadata.sign_from 39 | 40 | unpack_result = await unpack(resolvers_config_bob, pack_result.packed_msg) 41 | assert unpack_result.message == TEST_MESSAGE 42 | assert unpack_result.metadata == expected_metadata 43 | -------------------------------------------------------------------------------- /tests/unit/spec_test_vectors/test_pack_unpack_auth_crypt.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import pytest 4 | 5 | from didcomm.common.types import DID_OR_DID_URL 6 | from didcomm.pack_encrypted import pack_encrypted, PackEncryptedConfig 7 | from didcomm.unpack import unpack 8 | from tests.test_vectors.common import ALICE_DID, BOB_DID, TTestVector 9 | from tests.test_vectors.didcomm_messages.messages import TEST_MESSAGE 10 | from tests.test_vectors.didcomm_messages.spec.spec_test_vectors_auth_encrypted import ( 11 | TEST_ENCRYPTED_DIDCOMM_MESSAGE_AUTH, 12 | ) 13 | from tests.unit.common import ( 14 | check_unpack_test_vector, 15 | decode_jwe_headers, 16 | remove_signed_msg, 17 | ) 18 | 19 | 20 | @pytest.mark.asyncio 21 | @pytest.mark.parametrize("test_vector", TEST_ENCRYPTED_DIDCOMM_MESSAGE_AUTH) 22 | async def test_unpack_authcrypt(test_vector, resolvers_config_bob_all_in_secrets): 23 | await check_unpack_test_vector(test_vector, resolvers_config_bob_all_in_secrets) 24 | 25 | 26 | @pytest.mark.asyncio 27 | async def test_pack_authcrypt_sender_as_did_recipient_as_did( 28 | resolvers_config_alice, resolvers_config_bob 29 | ): 30 | await check_pack_authcrypt( 31 | frm=ALICE_DID, 32 | to=BOB_DID, 33 | sign_frm=None, 34 | pack_config=PackEncryptedConfig(forward=False), 35 | test_vector=TEST_ENCRYPTED_DIDCOMM_MESSAGE_AUTH[0], 36 | resolvers_config_alice=resolvers_config_alice, 37 | resolvers_config_bob=resolvers_config_bob, 38 | ) 39 | 40 | 41 | @pytest.mark.asyncio 42 | async def test_pack_authcrypt_signed_sender_as_kid_recipient_as_did( 43 | resolvers_config_alice, resolvers_config_bob 44 | ): 45 | test_vector = TEST_ENCRYPTED_DIDCOMM_MESSAGE_AUTH[1] 46 | await check_pack_authcrypt( 47 | frm=test_vector.metadata.encrypted_from, 48 | to=BOB_DID, 49 | sign_frm=ALICE_DID, 50 | pack_config=PackEncryptedConfig(forward=False), 51 | test_vector=test_vector, 52 | resolvers_config_alice=resolvers_config_alice, 53 | resolvers_config_bob=resolvers_config_bob, 54 | ) 55 | 56 | 57 | @pytest.mark.asyncio 58 | async def test_pack_authcrypt_signed_protect_sender_sender_as_kid_recipient_as_did( 59 | resolvers_config_alice, resolvers_config_bob 60 | ): 61 | test_vector = TEST_ENCRYPTED_DIDCOMM_MESSAGE_AUTH[2] 62 | await check_pack_authcrypt( 63 | frm=test_vector.metadata.encrypted_from, 64 | to=BOB_DID, 65 | sign_frm=test_vector.metadata.sign_from, 66 | pack_config=PackEncryptedConfig(protect_sender_id=True, forward=False), 67 | test_vector=test_vector, 68 | resolvers_config_alice=resolvers_config_alice, 69 | resolvers_config_bob=resolvers_config_bob, 70 | ) 71 | 72 | 73 | async def check_pack_authcrypt( 74 | frm: DID_OR_DID_URL, 75 | to: DID_OR_DID_URL, 76 | sign_frm: Optional[DID_OR_DID_URL], 77 | pack_config: PackEncryptedConfig, 78 | test_vector: TTestVector, 79 | resolvers_config_alice, 80 | resolvers_config_bob, 81 | ): 82 | expected_metadata = test_vector.metadata 83 | pack_result = await pack_encrypted( 84 | resolvers_config_alice, 85 | TEST_MESSAGE, 86 | frm=frm, 87 | to=to, 88 | sign_frm=sign_frm, 89 | pack_config=pack_config, 90 | ) 91 | 92 | pack_result_headers = decode_jwe_headers(pack_result.packed_msg) 93 | expected_headers = decode_jwe_headers(test_vector.value) 94 | assert pack_result_headers == expected_headers 95 | assert pack_result.to_kids == expected_metadata.encrypted_to 96 | assert pack_result.from_kid == expected_metadata.encrypted_from 97 | assert pack_result.sign_from_kid == expected_metadata.sign_from 98 | 99 | unpack_result = await unpack(resolvers_config_bob, pack_result.packed_msg) 100 | unpack_metadata_wo_signed_msg = remove_signed_msg(unpack_result.metadata) 101 | expected_metadata_wo_signed_msg = remove_signed_msg(expected_metadata) 102 | assert unpack_result.message == TEST_MESSAGE 103 | assert unpack_metadata_wo_signed_msg == expected_metadata_wo_signed_msg 104 | -------------------------------------------------------------------------------- /tests/unit/spec_test_vectors/test_pack_unpack_plaintext.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.core.serialization import json_str_to_dict 4 | from didcomm.pack_plaintext import pack_plaintext 5 | from didcomm.unpack import unpack 6 | from tests.test_vectors.didcomm_messages.messages import TEST_MESSAGE 7 | from tests.test_vectors.didcomm_messages.spec.spec_test_vectors_plaintext import ( 8 | PLAINTEXT_EXPECTED_METADATA, 9 | TEST_PLAINTEXT_DIDCOMM_MESSAGE_SIMPLE, 10 | ) 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_unpack_simple_plaintext(resolvers_config_bob): 15 | unpack_result = await unpack( 16 | resolvers_config_bob, TEST_PLAINTEXT_DIDCOMM_MESSAGE_SIMPLE 17 | ) 18 | assert unpack_result.metadata == PLAINTEXT_EXPECTED_METADATA 19 | assert unpack_result.message == TEST_MESSAGE 20 | 21 | 22 | @pytest.mark.asyncio 23 | async def test_pack_simple_plaintext(resolvers_config_bob): 24 | pack_result = await pack_plaintext(resolvers_config_bob, TEST_MESSAGE) 25 | assert json_str_to_dict(pack_result.packed_msg) == json_str_to_dict( 26 | TEST_PLAINTEXT_DIDCOMM_MESSAGE_SIMPLE 27 | ) 28 | -------------------------------------------------------------------------------- /tests/unit/spec_test_vectors/test_pack_unpack_signed.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | import pytest 4 | 5 | from didcomm.common.types import DID_OR_DID_URL 6 | from didcomm.pack_signed import pack_signed 7 | from didcomm.unpack import unpack 8 | from tests.test_vectors.common import ALICE_DID, TTestVector 9 | from tests.test_vectors.didcomm_messages.messages import TEST_MESSAGE 10 | from tests.test_vectors.didcomm_messages.spec.spec_test_vectors_signed import ( 11 | TEST_SIGNED_DIDCOMM_MESSAGE, 12 | ) 13 | from tests.unit.common import check_unpack_test_vector, decode_and_remove_jws_signatures 14 | 15 | 16 | @pytest.mark.asyncio 17 | @pytest.mark.parametrize("test_vector", TEST_SIGNED_DIDCOMM_MESSAGE) 18 | async def test_unpack_signed(test_vector, resolvers_config_bob): 19 | await check_unpack_test_vector(test_vector, resolvers_config_bob) 20 | 21 | 22 | @pytest.mark.asyncio 23 | async def test_pack_signed_by_did(resolvers_config_alice, resolvers_config_bob): 24 | await check_pack_signed( 25 | ALICE_DID, 26 | TEST_SIGNED_DIDCOMM_MESSAGE[0], 27 | resolvers_config_alice, 28 | resolvers_config_bob, 29 | ) 30 | 31 | 32 | @pytest.mark.asyncio 33 | @pytest.mark.parametrize("test_vector", TEST_SIGNED_DIDCOMM_MESSAGE) 34 | async def test_pack_signed_by_kid( 35 | test_vector, 36 | resolvers_config_alice, 37 | resolvers_config_bob, 38 | ): 39 | await check_pack_signed( 40 | test_vector.metadata.sign_from, 41 | test_vector, 42 | resolvers_config_alice, 43 | resolvers_config_bob, 44 | ) 45 | 46 | 47 | async def check_pack_signed( 48 | sign_frm: DID_OR_DID_URL, 49 | test_vector: TTestVector, 50 | resolvers_config_alice, 51 | resolvers_config_bob, 52 | ): 53 | expected_packed_msg = test_vector.value 54 | expected_metadata = copy.deepcopy(test_vector.metadata) 55 | 56 | pack_result = await pack_signed( 57 | resolvers_config_alice, TEST_MESSAGE, sign_frm=sign_frm 58 | ) 59 | pack_result_wo_signature = decode_and_remove_jws_signatures(pack_result.packed_msg) 60 | expected_result_wo_signature = decode_and_remove_jws_signatures(expected_packed_msg) 61 | assert pack_result_wo_signature == expected_result_wo_signature 62 | assert pack_result.sign_from_kid == expected_metadata.sign_from 63 | 64 | unpack_result = await unpack(resolvers_config_bob, pack_result.packed_msg) 65 | expected_metadata.signed_message = pack_result.packed_msg 66 | assert unpack_result.message == TEST_MESSAGE 67 | assert unpack_result.metadata == expected_metadata 68 | -------------------------------------------------------------------------------- /tests/unit/unpack/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/unit/unpack/__init__.py -------------------------------------------------------------------------------- /tests/unit/unpack/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture() 5 | def resolvers_config_alice(resolvers_config_alice_with_non_secrets): 6 | return resolvers_config_alice_with_non_secrets 7 | 8 | 9 | @pytest.fixture() 10 | def resolvers_config_bob(resolvers_config_bob_with_non_secrets): 11 | return resolvers_config_bob_with_non_secrets 12 | 13 | 14 | @pytest.fixture() 15 | def did_resolver(did_resolver_with_non_secrets): 16 | return did_resolver_with_non_secrets 17 | -------------------------------------------------------------------------------- /tests/unit/unpack/test_unpack_encrypted.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.asyncio 5 | async def test_unpack_anon_crypted(resolvers_config_bob): 6 | # TODO 7 | pass 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_unpack_anon_crypted_with_attachments(resolvers_config_bob): 12 | # TODO 13 | pass 14 | 15 | 16 | @pytest.mark.asyncio 17 | async def test_unpack_anon_crypted_signed(resolvers_config_bob): 18 | # TODO 19 | pass 20 | 21 | 22 | @pytest.mark.asyncio 23 | async def test_unpack_auth_crypted(resolvers_config_bob): 24 | # TODO 25 | pass 26 | 27 | 28 | @pytest.mark.asyncio 29 | async def test_unpack_auth_crypted_with_attachments(resolvers_config_bob): 30 | # TODO 31 | pass 32 | 33 | 34 | @pytest.mark.asyncio 35 | async def test_unpack_auth_crypted_signed(resolvers_config_bob): 36 | # TODO 37 | pass 38 | 39 | 40 | @pytest.mark.asyncio 41 | async def test_unpack_auth_crypted_signed_protected_header(resolvers_config_bob): 42 | # TODO 43 | pass 44 | -------------------------------------------------------------------------------- /tests/unit/unpack/test_unpack_encrypted_negative.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.errors import MalformedMessageError 4 | from didcomm.unpack import unpack 5 | from tests.test_vectors.common import TTestVectorNegative 6 | from tests.test_vectors.didcomm_messages.tests.test_vectors_anoncrypt_negative import ( 7 | INVALID_ANONCRYPT_TEST_VECTORS, 8 | ANONCRYPT_MESSAGE_P256_XC20P_EPK_WRONG_POINT, 9 | ) 10 | from tests.test_vectors.didcomm_messages.tests.test_vectors_authcrypt_negative import ( 11 | INVALID_AUTHCRYPT_TEST_VECTORS, 12 | ) 13 | 14 | 15 | @pytest.mark.asyncio 16 | @pytest.mark.parametrize("test_vector", INVALID_AUTHCRYPT_TEST_VECTORS) 17 | async def test_unpack_authcrypt_message( 18 | test_vector: TTestVectorNegative, resolvers_config_bob 19 | ): 20 | with pytest.raises(test_vector.exc): 21 | await unpack(resolvers_config_bob, test_vector.value) 22 | 23 | 24 | @pytest.mark.asyncio 25 | @pytest.mark.parametrize("test_vector", INVALID_ANONCRYPT_TEST_VECTORS) 26 | async def test_unpack_anoncrypt_message( 27 | test_vector: TTestVectorNegative, resolvers_config_bob 28 | ): 29 | with pytest.raises(test_vector.exc): 30 | await unpack(resolvers_config_bob, test_vector.value) 31 | 32 | 33 | @pytest.mark.asyncio 34 | async def test_unpack_anoncrypt_message_epk_wrong_point(resolvers_config_bob): 35 | with pytest.raises(MalformedMessageError): 36 | await unpack(resolvers_config_bob, ANONCRYPT_MESSAGE_P256_XC20P_EPK_WRONG_POINT) 37 | -------------------------------------------------------------------------------- /tests/unit/unpack/test_unpack_plaintext.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.unpack import unpack 4 | from tests.test_vectors.didcomm_messages.messages import ( 5 | TEST_MESSAGE, 6 | minimal_msg, 7 | attachment_base64_msg, 8 | attachment_links_msg, 9 | attachment_json_msg, 10 | attachment_multi_1_msg, 11 | attachment_multi_2_msg, 12 | ack_msg, 13 | ) 14 | from tests.test_vectors.didcomm_messages.spec.spec_test_vectors_plaintext import ( 15 | TEST_PLAINTEXT_DIDCOMM_MESSAGE_SIMPLE, 16 | PLAINTEXT_EXPECTED_METADATA, 17 | ) 18 | from tests.test_vectors.didcomm_messages.tests.test_vectors_plaintext_positive import ( 19 | TEST_PLAINTEXT_ATTACHMENT_BASE64, 20 | TEST_PLAINTEXT_ATTACHMENT_LINKS, 21 | TEST_PLAINTEXT_ATTACHMENT_JSON, 22 | TEST_PLAINTEXT_ATTACHMENT_MULTI_1, 23 | TEST_PLAINTEXT_ATTACHMENT_MULTI_2, 24 | TEST_PLAINTEXT_DIDCOMM_MESSAGE_MINIMAL, 25 | TEST_PLAINTEXT_ACKS, 26 | TEST_PLAINTEXT_DIDCOMM_MESSAGE_MINIMAL_NO_TYP, 27 | ) 28 | 29 | 30 | @pytest.mark.asyncio 31 | async def test_unpack_simple_plaintext(resolvers_config_bob): 32 | unpack_result = await unpack( 33 | resolvers_config_bob, TEST_PLAINTEXT_DIDCOMM_MESSAGE_SIMPLE 34 | ) 35 | assert unpack_result.metadata == PLAINTEXT_EXPECTED_METADATA 36 | assert unpack_result.message == TEST_MESSAGE 37 | 38 | 39 | @pytest.mark.asyncio 40 | async def test_unpack_simple_minimal(resolvers_config_bob): 41 | unpack_result = await unpack( 42 | resolvers_config_bob, TEST_PLAINTEXT_DIDCOMM_MESSAGE_MINIMAL 43 | ) 44 | assert unpack_result.metadata == PLAINTEXT_EXPECTED_METADATA 45 | assert unpack_result.message == minimal_msg() 46 | 47 | 48 | @pytest.mark.asyncio 49 | async def test_unpack_simple_minimal_no_typ(resolvers_config_bob): 50 | unpack_result = await unpack( 51 | resolvers_config_bob, TEST_PLAINTEXT_DIDCOMM_MESSAGE_MINIMAL_NO_TYP 52 | ) 53 | assert unpack_result.metadata == PLAINTEXT_EXPECTED_METADATA 54 | assert unpack_result.message == minimal_msg() 55 | 56 | 57 | @pytest.mark.asyncio 58 | async def test_unpack_attachments_base64(resolvers_config_bob): 59 | unpack_result = await unpack(resolvers_config_bob, TEST_PLAINTEXT_ATTACHMENT_BASE64) 60 | assert unpack_result.metadata == PLAINTEXT_EXPECTED_METADATA 61 | assert unpack_result.message == attachment_base64_msg() 62 | 63 | 64 | @pytest.mark.asyncio 65 | async def test_unpack_attachments_links(resolvers_config_bob): 66 | unpack_result = await unpack(resolvers_config_bob, TEST_PLAINTEXT_ATTACHMENT_LINKS) 67 | assert unpack_result.metadata == PLAINTEXT_EXPECTED_METADATA 68 | assert unpack_result.message == attachment_links_msg() 69 | 70 | 71 | @pytest.mark.asyncio 72 | async def test_unpack_attachments_json(resolvers_config_bob): 73 | unpack_result = await unpack(resolvers_config_bob, TEST_PLAINTEXT_ATTACHMENT_JSON) 74 | assert unpack_result.metadata == PLAINTEXT_EXPECTED_METADATA 75 | assert unpack_result.message == attachment_json_msg() 76 | 77 | 78 | @pytest.mark.asyncio 79 | async def test_unpack_attachments_multi_1(resolvers_config_bob): 80 | unpack_result = await unpack( 81 | resolvers_config_bob, TEST_PLAINTEXT_ATTACHMENT_MULTI_1 82 | ) 83 | assert unpack_result.metadata == PLAINTEXT_EXPECTED_METADATA 84 | assert unpack_result.message == attachment_multi_1_msg() 85 | 86 | 87 | @pytest.mark.asyncio 88 | async def test_unpack_attachments_multi_2(resolvers_config_bob): 89 | unpack_result = await unpack( 90 | resolvers_config_bob, TEST_PLAINTEXT_ATTACHMENT_MULTI_2 91 | ) 92 | assert unpack_result.metadata == PLAINTEXT_EXPECTED_METADATA 93 | assert unpack_result.message == attachment_multi_2_msg() 94 | 95 | 96 | @pytest.mark.asyncio 97 | async def test_unpack_ack(resolvers_config_bob): 98 | unpack_result = await unpack(resolvers_config_bob, TEST_PLAINTEXT_ACKS) 99 | assert unpack_result.metadata == PLAINTEXT_EXPECTED_METADATA 100 | assert unpack_result.message == ack_msg() 101 | -------------------------------------------------------------------------------- /tests/unit/unpack/test_unpack_plaintext_negative.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.unpack import unpack 4 | from tests.test_vectors.common import TTestVectorNegative 5 | from tests.test_vectors.didcomm_messages.tests.test_vectors_plaintext_negative import ( 6 | INVALID_PLAINTEXT_TEST_VECTORS, 7 | ) 8 | 9 | 10 | @pytest.mark.asyncio 11 | @pytest.mark.parametrize("test_vector", INVALID_PLAINTEXT_TEST_VECTORS) 12 | async def test_unpack_invalid_message( 13 | test_vector: TTestVectorNegative, resolvers_config_bob 14 | ): 15 | with pytest.raises(test_vector.exc): 16 | await unpack(resolvers_config_bob, test_vector.value) 17 | -------------------------------------------------------------------------------- /tests/unit/unpack/test_unpack_plaintext_with_from_prior.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from authlib.common.encoding import json_dumps 3 | 4 | from didcomm.core.serialization import json_str_to_dict 5 | from didcomm.unpack import unpack 6 | from tests.test_vectors.didcomm_messages.messages import TEST_MESSAGE_FROM_PRIOR 7 | 8 | PACKED_MESSAGE = json_dumps( 9 | { 10 | "id": "1234567890", 11 | "typ": "application/didcomm-plain+json", 12 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 13 | "from": "did:example:alice", 14 | "to": ["did:example:bob"], 15 | "created_time": 1516269022, 16 | "expires_time": 1516385931, 17 | "from_prior": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDpleGFtcGxlOmNoYXJsaWUja2V5LTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTpjaGFybGllIiwic3ViIjoiZGlkOmV4YW1wbGU6YWxpY2UiLCJhdWQiOiIxMjMiLCJleHAiOjEyMzQsIm5iZiI6MTIzNDUsImlhdCI6MTIzNDU2LCJqdGkiOiJkZmcifQ.xIdfOLizPZMOZPAqMatEuBk0OrVge6jN1nxYlWUISz0XUXdW6TbwDbMgTinx2CLxtxev_gqEuZw5c98E3-eaAQ", 18 | "body": {"messagespecificattribute": "and its value"}, 19 | } 20 | ) 21 | 22 | 23 | @pytest.mark.asyncio 24 | async def test_unpack_plaintext_with_from_prior( 25 | resolvers_config_bob, 26 | ): 27 | unpack_result = await unpack(resolvers_config_bob, PACKED_MESSAGE) 28 | 29 | assert unpack_result.message == TEST_MESSAGE_FROM_PRIOR 30 | assert unpack_result.metadata.from_prior_issuer_kid == "did:example:charlie#key-1" 31 | assert ( 32 | unpack_result.metadata.from_prior_jwt 33 | == json_str_to_dict(PACKED_MESSAGE)["from_prior"] 34 | ) 35 | -------------------------------------------------------------------------------- /tests/unit/unpack/test_unpack_plaintext_with_from_prior_negative.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from authlib.common.encoding import json_dumps 3 | 4 | from didcomm.errors import MalformedMessageError 5 | from didcomm.unpack import unpack 6 | from tests.test_vectors.common import TTestVectorNegative 7 | 8 | PACKED_MESSAGE_INVALID_FROM_PRIOR = json_dumps( 9 | { 10 | "id": "1234567890", 11 | "typ": "application/didcomm-plain+json", 12 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 13 | "from": "did:example:alice", 14 | "to": ["did:example:bob"], 15 | "created_time": 1516269022, 16 | "expires_time": 1516385931, 17 | "from_prior": "invalid", 18 | "body": {"messagespecificattribute": "and its value"}, 19 | } 20 | ) 21 | 22 | 23 | PACKED_MESSAGE_INVALID_FROM_PRIOR_SIGNATURE = json_dumps( 24 | { 25 | "id": "1234567890", 26 | "typ": "application/didcomm-plain+json", 27 | "type": "http://example.com/protocols/lets_do_lunch/1.0/proposal", 28 | "from": "did:example:alice", 29 | "to": ["did:example:bob"], 30 | "created_time": 1516269022, 31 | "expires_time": 1516385931, 32 | "from_prior": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpleGFtcGxlOmNoYXJsaWUja2V5LTEifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTpjaGFybGllIiwic3ViIjoiZGlkOmV4YW1wbGU6YWxpY2UiLCJhdWQiOiIxMjMiLCJleHAiOjEyMzQsIm5iZiI6MTIzNDUsImlhdCI6MTIzNDU2LCJqdGkiOiJkZmcifQ.9F1o6duu_lC6LTZN-RN3R6uUl_p63ma30i8nNu2xoCmOc-lE9G1z1-iZ2jZ81kFmq5aOMyhcVXat6TOGJdcVD", 33 | "body": {"messagespecificattribute": "and its value"}, 34 | } 35 | ) 36 | 37 | 38 | INVALID_TEST_VECTORS = [ 39 | TTestVectorNegative(PACKED_MESSAGE_INVALID_FROM_PRIOR, MalformedMessageError), 40 | TTestVectorNegative( 41 | PACKED_MESSAGE_INVALID_FROM_PRIOR_SIGNATURE, MalformedMessageError 42 | ), 43 | ] 44 | 45 | 46 | @pytest.mark.parametrize("test_vector", INVALID_TEST_VECTORS) 47 | @pytest.mark.asyncio 48 | async def test_unpack_plaintext_with_invalid_from_prior( 49 | test_vector, 50 | resolvers_config_bob, 51 | ): 52 | with pytest.raises(test_vector.exc): 53 | await unpack(resolvers_config_bob, test_vector.value) 54 | -------------------------------------------------------------------------------- /tests/unit/unpack/test_unpack_signed.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.asyncio 5 | async def test_unpack_signed(resolvers_config_bob): 6 | # TODO 7 | pass 8 | 9 | 10 | @pytest.mark.asyncio 11 | async def test_unpack_signed_with_attachments(resolvers_config_bob): 12 | # TODO 13 | pass 14 | -------------------------------------------------------------------------------- /tests/unit/unpack/test_unpack_signed_negative.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from didcomm.unpack import unpack 4 | from tests.test_vectors.common import TTestVectorNegative 5 | from tests.test_vectors.didcomm_messages.tests.test_vectors_signed_negative import ( 6 | INVALID_SIGNED_TEST_VECTORS, 7 | ) 8 | 9 | 10 | @pytest.mark.asyncio 11 | @pytest.mark.parametrize("test_vector", INVALID_SIGNED_TEST_VECTORS) 12 | async def test_unpack_anoncrypt_message( 13 | test_vector: TTestVectorNegative, resolvers_config_bob 14 | ): 15 | with pytest.raises(test_vector.exc): 16 | await unpack(resolvers_config_bob, test_vector.value) 17 | -------------------------------------------------------------------------------- /tests/unit/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sicpa-dlab/didcomm-python/8379164b709c3287bbcd84b42a3045c8c71ca210/tests/unit/utils/__init__.py -------------------------------------------------------------------------------- /tests/unit/utils/test_utils.py: -------------------------------------------------------------------------------- 1 | from didcomm.core.utils import is_did 2 | 3 | 4 | def test_is_did(): 5 | assert is_did("did:example:alice") 6 | assert is_did("did:example:alice:alice2") 7 | assert is_did("did:example:alice#key-1") 8 | assert is_did("did:example:alice:alice2#key-1") 9 | 10 | assert not is_did("did:example") 11 | assert not is_did("did") 12 | assert not is_did("did:example#key-1") 13 | --------------------------------------------------------------------------------