From 449acff1971fac889943925721452bcb87932d7d Mon Sep 17 00:00:00 2001 From: Albin Antony Date: Tue, 7 May 2024 17:46:45 +0530 Subject: [PATCH] Fix #16: Support custom cryptographic seed while creating/updating organisation for creating did:key identifier --- alembic/versions/9d0d8756d0e3_.py | 30 ++++++++++++++++++ .../ebsi/entry_points/server/decorators.py | 3 +- .../entry_points/server/routes/v2/config.py | 31 ++++++++++++++++--- eudi_wallet/ebsi/models/organisation.py | 1 + .../services/application/v2_organisation.py | 17 +++++----- eudi_wallet/ebsi/services/domain/utils/did.py | 29 +++++++++++++++++ .../register_organisation_usecase.py | 4 ++- .../update_organisation_usecase.py | 4 +++ 8 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 alembic/versions/9d0d8756d0e3_.py diff --git a/alembic/versions/9d0d8756d0e3_.py b/alembic/versions/9d0d8756d0e3_.py new file mode 100644 index 0000000..c58de8c --- /dev/null +++ b/alembic/versions/9d0d8756d0e3_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 9d0d8756d0e3 +Revises: 3be60cbb77ee +Create Date: 2024-05-09 11:51:16.326563 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '9d0d8756d0e3' +down_revision: Union[str, None] = '3be60cbb77ee' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('organisation', sa.Column('cryptographic_salt', sa.String(length=500), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('organisation', 'cryptographic_salt') + # ### end Alembic commands ### diff --git a/eudi_wallet/ebsi/entry_points/server/decorators.py b/eudi_wallet/ebsi/entry_points/server/decorators.py index 4c25942..975b882 100644 --- a/eudi_wallet/ebsi/entry_points/server/decorators.py +++ b/eudi_wallet/ebsi/entry_points/server/decorators.py @@ -1,6 +1,7 @@ import dataclasses from functools import wraps from typing import Optional, Tuple +import uuid from aiohttp import web @@ -191,7 +192,7 @@ async def wrapper(request): raise web.HTTPBadRequest(text="Legal entity not found") else: await legal_entity_service.set_cryptographic_seed( - crypto_seed=legal_entity_entity.cryptographic_seed # type: ignore + crypto_seed=legal_entity_entity.cryptographic_seed, salt = legal_entity_entity.cryptographic_salt # type: ignore ) await legal_entity_service.set_entity( legal_entity_entity=legal_entity_entity diff --git a/eudi_wallet/ebsi/entry_points/server/routes/v2/config.py b/eudi_wallet/ebsi/entry_points/server/routes/v2/config.py index 34490ea..fc8cc10 100644 --- a/eudi_wallet/ebsi/entry_points/server/routes/v2/config.py +++ b/eudi_wallet/ebsi/entry_points/server/routes/v2/config.py @@ -1,5 +1,6 @@ import json from typing import Optional, cast +import uuid from aiohttp import web from aiohttp.web_request import Request @@ -58,7 +59,7 @@ CreateCredentialOfferError, UpdateCredentialOfferError, ) -from eudi_wallet.ebsi.services.domain.utils.did import generate_and_store_did +from eudi_wallet.ebsi.services.domain.utils.did import generate_and_store_did_v2 from eudi_wallet.ebsi.utils.common import ( validate_data_attribute_schema_against_data_attribute_values, ) @@ -68,11 +69,11 @@ ) from eudi_wallet.ebsi.usecases.v2.organisation.read_verification_request_usecase import ( ReadVerificationRequestUsecase, - ReadVerificationRequestUsecaseError + ReadVerificationRequestUsecaseError, ) from eudi_wallet.ebsi.usecases.v2.organisation.delete_verification_request_usecase import ( DeleteVerificationRequestUsecase, - DeleteVerificationRequestUsecaseError + DeleteVerificationRequestUsecaseError, ) from eudi_wallet.ebsi.usecases.v2.organisation.list_verification_request_usecase import ( @@ -90,8 +91,23 @@ async def handle_config_get_organisation_identifier( request: Request, context: V2RequestContext ): - _, ebsi_did, key_did = await generate_and_store_did( - context.legal_entity_service.legal_entity_entity.cryptographic_seed + organisation_id = request.match_info.get("organisationId") + if organisation_id is None: + raise web.HTTPBadRequest(reason="Invalid organisation id") + + if context.legal_entity_service.legal_entity_entity.cryptographic_salt is None: + usecase = UpdateOrganisationUsecase( + organisation_repository=context.organisation_repository, + logger=context.app_context.logger, + ) + organisation = usecase.execute( + organisation_id=organisation_id, + name=context.legal_entity_service.legal_entity_entity.name, + cryptographic_salt=uuid.uuid4().hex, + ) + _, ebsi_did, key_did = await generate_and_store_did_v2( + context.legal_entity_service.legal_entity_entity.cryptographic_seed, + salt=context.legal_entity_service.legal_entity_entity.cryptographic_salt, ) resp = { @@ -113,6 +129,7 @@ class RegisterOrganisationReq(BaseModel): constr(min_length=1, max_length=500, strip_whitespace=True) # type: ignore ] = None webhookUrl: Optional[HttpUrl] = None + cryptographicSeed: Optional[str] = None @config_routes.post( @@ -146,6 +163,7 @@ async def handle_config_post_register_organisation( cover_image_url=cover_image_url, webhook_url=webhook_url, location=register_organisation_req.location, + cryptographic_seed=register_organisation_req.cryptographicSeed, ) return web.json_response(organisation.to_dict()) @@ -199,6 +217,7 @@ class UpdateOrganisationReq(BaseModel): constr(min_length=1, max_length=500, strip_whitespace=True) # type: ignore ] = None webhookUrl: Optional[HttpUrl] = None + cryptographicSeed: Optional[str] = None @config_routes.put( @@ -226,6 +245,7 @@ async def handle_config_put_update_organisation( logo_url = str(update_organisation_req.logoUrl) cover_image_url = str(update_organisation_req.coverImageUrl) webhook_url = str(update_organisation_req.webhookUrl) + cryptographic_seed = update_organisation_req.cryptographicSeed organisation = usecase.execute( organisation_id=organisation_id, name=update_organisation_req.name, @@ -234,6 +254,7 @@ async def handle_config_put_update_organisation( cover_image_url=cover_image_url, webhook_url=webhook_url, location=update_organisation_req.location, + cryptographic_seed=cryptographic_seed, ) if not organisation: raise web.HTTPBadRequest(reason="Organisation not found") diff --git a/eudi_wallet/ebsi/models/organisation.py b/eudi_wallet/ebsi/models/organisation.py index 20188e8..3f6e564 100644 --- a/eudi_wallet/ebsi/models/organisation.py +++ b/eudi_wallet/ebsi/models/organisation.py @@ -32,6 +32,7 @@ class OrganisationModel(Base): description = Column(String(length=500), nullable=True) logo_url = Column(Text, nullable=True) cryptographic_seed = Column(String(length=500), nullable=False) + cryptographic_salt = Column(String(length=500), nullable=True,default=uuid.uuid4().hex) role = Column(String(length=200), nullable=False) location = Column(String(length=100), nullable=True) cover_image_url = Column(Text, nullable=True) diff --git a/eudi_wallet/ebsi/services/application/v2_organisation.py b/eudi_wallet/ebsi/services/application/v2_organisation.py index ebd47e5..d3bc4e8 100644 --- a/eudi_wallet/ebsi/services/application/v2_organisation.py +++ b/eudi_wallet/ebsi/services/application/v2_organisation.py @@ -51,7 +51,7 @@ from eudi_wallet.ebsi.services.domain.utils.credential import ( create_credential_token, ) -from eudi_wallet.ebsi.services.domain.utils.did import generate_and_store_did +from eudi_wallet.ebsi.services.domain.utils.did import generate_and_store_did_v2 from eudi_wallet.ebsi.utils.date_time import generate_ISO8601_UTC from eudi_wallet.ebsi.utils.jwt import decode_header_and_claims_in_jwt from eudi_wallet.ebsi.value_objects.application.organisation import ( @@ -125,10 +125,11 @@ async def get_legal_entity( with self.legal_entity_repository as repo: return repo.get_by_id(id=organisation_id) - async def set_cryptographic_seed(self, crypto_seed: str) -> None: + async def set_cryptographic_seed(self, crypto_seed: str, salt: str) -> None: self.crypto_seed = crypto_seed - self.eth, self.ebsi_did, self.key_did = await generate_and_store_did( - crypto_seed + + self.eth, self.ebsi_did, self.key_did = await generate_and_store_did_v2( + crypto_seed=self.crypto_seed, salt=salt ) async def set_entity( @@ -399,9 +400,11 @@ async def get_credential_offer_without_data_agreement( "credentials": [ { "format": format, - "types": credential.get("type") - if credential.get("type") - else ["VerifiableCredential"], + "types": ( + credential.get("type") + if credential.get("type") + else ["VerifiableCredential"] + ), "trust_framework": { "name": "ebsi", "type": "Accreditation", diff --git a/eudi_wallet/ebsi/services/domain/utils/did.py b/eudi_wallet/ebsi/services/domain/utils/did.py index 63d3ec3..653c73f 100644 --- a/eudi_wallet/ebsi/services/domain/utils/did.py +++ b/eudi_wallet/ebsi/services/domain/utils/did.py @@ -1,4 +1,5 @@ import typing +import hashlib from eudi_wallet.did_key import KeyDid, PublicKeyJWK from eudi_wallet.ebsi_did import EbsiDid @@ -27,3 +28,31 @@ async def generate_and_store_did( key_did.generate_did(public_key_jwk) return eth, ebsi_did, key_did + + +async def generate_and_store_did_v2( + crypto_seed: str, + salt: str = None, +) -> typing.Tuple[Ethereum, EbsiDid, KeyDid]: + if salt is not None: + crypto_seed = hashlib.sha256((crypto_seed + salt).encode("utf-8")).digest() + else: + crypto_seed = crypto_seed.encode("utf-8") + + # Generate EBSI DID for legal entity + eth = Ethereum(seed=crypto_seed) + ebsi_did = EbsiDid(seed=crypto_seed) + ebsi_did.generate_did(eth=eth) + + # Generate EBSI DID for natural person + key_did = KeyDid(seed=crypto_seed) + key_did.create_keypair() + public_key_jwk = PublicKeyJWK( + kty=key_did.public_key_jwk["kty"], + crv=key_did.public_key_jwk["crv"], + x=key_did.public_key_jwk["x"], + y=key_did.public_key_jwk["y"], + ) + key_did.generate_did(public_key_jwk) + + return eth, ebsi_did, key_did diff --git a/eudi_wallet/ebsi/usecases/v2/organisation/register_organisation_usecase.py b/eudi_wallet/ebsi/usecases/v2/organisation/register_organisation_usecase.py index d954e8f..19b2dea 100644 --- a/eudi_wallet/ebsi/usecases/v2/organisation/register_organisation_usecase.py +++ b/eudi_wallet/ebsi/usecases/v2/organisation/register_organisation_usecase.py @@ -26,12 +26,14 @@ def execute( cover_image_url: Optional[str] = None, webhook_url: Optional[str] = None, location: Optional[str] = None, + cryptographic_seed: Optional[str] = None, ) -> OrganisationModel: # Create an organistion in db with self.organisation_repository as repo: # mnemo = Mnemonic("english") # seed_phrase = mnemo.generate(strength=256) - cryptographic_seed = str(time.time()) + if cryptographic_seed is None: + cryptographic_seed = str(time.time()) return repo.create( name=name, cryptographic_seed=cryptographic_seed, diff --git a/eudi_wallet/ebsi/usecases/v2/organisation/update_organisation_usecase.py b/eudi_wallet/ebsi/usecases/v2/organisation/update_organisation_usecase.py index 423cc0e..19681a1 100644 --- a/eudi_wallet/ebsi/usecases/v2/organisation/update_organisation_usecase.py +++ b/eudi_wallet/ebsi/usecases/v2/organisation/update_organisation_usecase.py @@ -21,6 +21,8 @@ def execute( cover_image_url: Optional[str] = None, webhook_url: Optional[str] = None, location: Optional[str] = None, + cryptographic_seed: Optional[str] = None, + cryptographic_salt: Optional[str] = None, ) -> OrganisationModel: # Update an organisation with self.organisation_repository as repo: @@ -32,4 +34,6 @@ def execute( cover_image_url=cover_image_url, webhook_url=webhook_url, location=location, + cryptographic_seed=cryptographic_seed, + cryptographic_salt=cryptographic_salt, )