Skip to content

Commit

Permalink
add more verbose error message and make the version a variable
Browse files Browse the repository at this point in the history
Signed-off-by: pstlouis <[email protected]>
  • Loading branch information
PatStLouis committed Jan 13, 2025
1 parent 51ca877 commit b39fa27
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 99 deletions.
2 changes: 1 addition & 1 deletion server/app/models/di_proof.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class DataIntegrityProof(BaseModel):
challenge: str = Field(None)
created: str = Field(None)
expires: str = Field(None)

model_config = {
"json_schema_extra": {
"examples": [
Expand Down
10 changes: 5 additions & 5 deletions server/app/models/did_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class VerificationMethod(BaseModel):
@field_validator("id")
@classmethod
def verification_method_id_validator(cls, value):
assert value.startswith('did:')
assert value.startswith("did:")
return value

@field_validator("type")
Expand All @@ -42,7 +42,7 @@ def verification_method_type_validator(cls, value):
@field_validator("controller")
@classmethod
def verification_method_controller_validator(cls, value):
assert value.startswith('did:')
assert value.startswith("did:")
return value


Expand Down Expand Up @@ -83,7 +83,7 @@ class Service(BaseModel):
@field_validator("id")
@classmethod
def service_id_validator(cls, value):
assert value.startswith('did:')
assert value.startswith("did:")
return value

@field_validator("serviceEndpoint")
Expand Down Expand Up @@ -112,7 +112,7 @@ class DidDocument(BaseModel):
capabilityInvocation: List[Union[str, VerificationMethod]] = Field(None)
capabilityDelegation: List[Union[str, VerificationMethod]] = Field(None)
service: List[Service] = Field(None)

model_config = {
"json_schema_extra": {
"examples": [
Expand All @@ -133,7 +133,7 @@ def context_validator(cls, value):
@field_validator("id")
@classmethod
def id_validator(cls, value):
assert value.startswith('did:')
assert value.startswith("did:")
return value


Expand Down
5 changes: 3 additions & 2 deletions server/app/models/did_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pydantic import BaseModel, Field
from .did_document import DidDocument
from .di_proof import DataIntegrityProof
from config import settings


class BaseModel(BaseModel):
Expand All @@ -26,7 +27,7 @@ class WitnessSignature(BaseModel):


class InitialLogParameters(BaseModel):
method: str = Field('did:webvh:0.5')
method: str = Field(f"did:webvh:{settings.WEBVH_VERSION}")
scid: str = Field()
updateKeys: List[str] = Field()
prerotation: bool = Field(default=False)
Expand All @@ -45,7 +46,7 @@ class LogParameters(BaseModel):
ttl: bool = Field(None)
method: str = Field(None)
scid: str = Field(None)

model_config = {
"json_schema_extra": {
"examples": [
Expand Down
2 changes: 1 addition & 1 deletion server/app/models/web_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class RegisterInitialLogEntry(BaseModel):
class UpdateLogEntry(BaseModel):
logEntry: LogEntry = Field()
witnessProof: List[DataIntegrityProof] = Field(None)

# model_config = {
# "json_schema_extra": {
# "examples": [
Expand Down
2 changes: 1 addition & 1 deletion server/app/plugins/askar.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def validate_proof(self, proof):

def verify_proof(self, document, proof):
self.validate_proof(proof)

multikey = proof["verificationMethod"].split("#")[-1]

key = Key(LocalKeyHandle()).from_public_bytes(
Expand Down
9 changes: 4 additions & 5 deletions server/app/plugins/didwebvh.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ def _init_parameters(self, update_key, next_key=None, ttl=100):
return parameters

def _init_state(self, did_doc):
return json.loads(json.dumps(did_doc).replace("did:web:", self.prefix + r"{SCID}:"))
return json.loads(
json.dumps(did_doc).replace("did:web:", self.prefix + r"{SCID}:")
)

def _generate_scid(self, log_entry):
# https://identity.foundation/trustdidweb/#generate-scid
Expand All @@ -37,10 +39,7 @@ def _generate_entry_hash(self, log_entry):
return encoded

def create_initial_did_doc(self, did_string):
did_doc = {
'@context': [],
'id': did_string
}
did_doc = {"@context": [], "id": did_string}
return log_entry

def create(self, did_doc, update_key):
Expand Down
49 changes: 27 additions & 22 deletions server/app/routers/identifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,28 @@

router = APIRouter(tags=["Identifiers"])


# DIDWeb
@router.get("/")
async def request_did(
namespace: str = None,
identifier: str = None,
):
if namespace and identifier:
client_id = f'{namespace}:{identifier}'
client_id = f"{namespace}:{identifier}"
did = f"{settings.DID_WEB_BASE}:{client_id}"
await identifier_available(did)
return JSONResponse(
status_code=200,
content={
"didDocument": DidDocument(
id=did
).model_dump(),
"didDocument": DidDocument(id=did).model_dump(),
"proofOptions": AskarVerifier().create_proof_config(did),
},
)

raise HTTPException(status_code=400, detail="Bad Request")
raise HTTPException(
status_code=400, detail="Missing namespace or identifier query."
)


@router.post("/")
Expand All @@ -44,7 +45,9 @@ async def register_did(
# Assert proof set
proof_set = did_document.pop("proof", None)
if len(proof_set) != 2:
raise HTTPException(status_code=400, detail="Bad Request")
raise HTTPException(
status_code=400, detail="Expecting proof set from controller and endorser."
)

# Find proof matching endorser
endorser_proof = next(
Expand Down Expand Up @@ -84,16 +87,13 @@ async def register_did(
initial_log_entry = DidWebVH().create(did_document, authorized_key)
return JSONResponse(status_code=201, content={"logEntry": initial_log_entry})

raise HTTPException(status_code=400, detail="Bad Request")
raise HTTPException(status_code=400, detail="Bad Request, something went wrong.")


# DIDWebVH
@router.get("/{namespace}/{identifier}")
async def get_log_state(
namespace: str,
identifier: str
):
client_id = f'{namespace}:{identifier}'
async def get_log_state(namespace: str, identifier: str):
client_id = f"{namespace}:{identifier}"
log_entry = await AskarStorage().fetch("logEntries", client_id)
if not log_entry:
did = f"{settings.DID_WEB_BASE}:{client_id}"
Expand All @@ -102,22 +102,25 @@ async def get_log_state(
initial_log_entry = DidWebVH().create(did_document, authorized_key)
return JSONResponse(status_code=200, content={"logEntry": initial_log_entry})
return JSONResponse(status_code=200, content={})


@router.post("/{namespace}/{identifier}")
async def create_didwebvh(
namespace: str,
identifier: str,
request_body: RegisterInitialLogEntry,
):
client_id = f'{namespace}:{identifier}'
client_id = f"{namespace}:{identifier}"
log_entry = request_body.model_dump()["logEntry"]
did = f"{settings.DID_WEB_BASE}:{namespace}:{identifier}"

# Assert proof set
proof = log_entry.pop("proof", None)
proof = proof if isinstance(proof, dict) else [proof]
if len(proof) != 1:
raise HTTPException(status_code=400, detail="Bad Request")
raise HTTPException(
status_code=400, detail="Expecting singular proof from controller."
)

# Verify proofs
proof = proof[0]
Expand All @@ -130,9 +133,9 @@ async def create_didwebvh(

AskarVerifier().verify_proof(log_entry, proof)
log_entry["proof"] = [proof]

await AskarStorage().store("logEntries", client_id, [log_entry])

did_document = await AskarStorage().fetch("didDocument", did)
did_document["alsoKnownAs"] = [log_entry["state"]["id"]]
await AskarStorage().update("didDocument", did, did_document)
Expand All @@ -144,31 +147,33 @@ async def read_did(namespace: str, identifier: str):
"""
https://identity.foundation/didwebvh/next/#read-resolve
"""
client_id = f'{namespace}:{identifier}'
client_id = f"{namespace}:{identifier}"
did = f"{settings.DID_WEB_BASE}:{namespace}:{identifier}"
did_doc = await AskarStorage().fetch("didDocument", did)
if did_doc:
return Response(json.dumps(did_doc), media_type="application/did+ld+json")
raise HTTPException(status_code=404, detail="Not Found")


@router.get("/{namespace}/{identifier}/did.jsonl", include_in_schema=False)
async def read_did_log(namespace: str, identifier: str):
"""
https://identity.foundation/didwebvh/next/#read-resolve
"""
client_id = f'{namespace}:{identifier}'
client_id = f"{namespace}:{identifier}"
log_entries = await AskarStorage().fetch("logEntries", client_id)
if log_entries:
log_entries = "\n".join([json.dumps(log_entry) for log_entry in log_entries])
return Response(log_entries, media_type="text/jsonl")
raise HTTPException(status_code=404, detail="Not Found")


@router.put("/{namespace}/{identifier}")
async def update_did(namespace: str, identifier: str, request_body: UpdateLogEntry):
"""
https://identity.foundation/didwebvh/next/#update-rotate
"""
client_id = f'{namespace}:{identifier}'
client_id = f"{namespace}:{identifier}"
raise HTTPException(status_code=501, detail="Not Implemented")


Expand All @@ -177,5 +182,5 @@ async def deactivate_did(namespace: str, identifier: str):
"""
https://identity.foundation/didwebvh/next/#deactivate-revoke
"""
client_id = f'{namespace}:{identifier}'
client_id = f"{namespace}:{identifier}"
raise HTTPException(status_code=501, detail="Not Implemented")
9 changes: 5 additions & 4 deletions server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ class Settings(BaseSettings):
PROJECT_TITLE: str = "DID WebVH Server"
PROJECT_VERSION: str = "v0"

SECRET_KEY: str = os.environ.get("SECRET_KEY", 's3cret')
SECRET_KEY: str = os.environ.get("SECRET_KEY", "s3cret")
WEBVH_VERSION: str = os.environ.get("WEBVH_VERSION", "0.4")

DOMAIN: str = os.environ.get("DOMAIN", 'localhost')
DOMAIN: str = os.environ.get("DOMAIN", "localhost")
DID_WEB_PREFIX: str = "did:web:"
DID_WEBVH_PREFIX: str = "did:webvh:"
DID_WEB_BASE: str = f"{DID_WEB_PREFIX}{DOMAIN}"
ENDORSER_MULTIKEY: str = os.environ.get("ENDORSER_MULTIKEY", '')
ENDORSER_MULTIKEY: str = os.environ.get("ENDORSER_MULTIKEY", "")

POSTGRES_USER: str = os.getenv("POSTGRES_USER", "")
POSTGRES_PASSWORD: str = os.getenv("POSTGRES_PASSWORD", "")
Expand All @@ -41,4 +42,4 @@ class Settings(BaseSettings):
SCID_PLACEHOLDER: str = "{SCID}"


settings = Settings()
settings = Settings()
21 changes: 10 additions & 11 deletions server/tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@
from app.models.did_document import DidDocument, SecuredDidDocument
from app.models.di_proof import DataIntegrityProof

TEST_SEED = 'ixUwS8A2SYzmPiGor7t08wgg1ifNABrB'
TEST_AUTHORISED_KEY = 'z6Mkixacx8HJ5nRBJvJKNdv83v1ejZBpz3HvRCfa2JaKbQJV'
TEST_AUTHORISED_JWK = 'QvGYHF-i-RTVnJlSDsYkSffG1GUZasgGt1yhRdv4rgI'
TEST_SEED = "ixUwS8A2SYzmPiGor7t08wgg1ifNABrB"
TEST_AUTHORISED_KEY = "z6Mkixacx8HJ5nRBJvJKNdv83v1ejZBpz3HvRCfa2JaKbQJV"
TEST_AUTHORISED_JWK = "QvGYHF-i-RTVnJlSDsYkSffG1GUZasgGt1yhRdv4rgI"
TEST_DOMAIN = settings.DOMAIN
TEST_DID_NAMESPACE = 'test'
TEST_DID_IDENTIFIER = '01'
TEST_DID_NAMESPACE = "test"
TEST_DID_IDENTIFIER = "01"
TEST_DID = f"{settings.DID_WEB_BASE}:{TEST_DID_NAMESPACE}:{TEST_DID_IDENTIFIER}"
TEST_PROOF_OPTIONS = {
'type': 'DataIntegrityProof',
'cryptosuite': 'eddsa-jcs-2022',
'proofPurpose': 'assertionMethod'
"type": "DataIntegrityProof",
"cryptosuite": "eddsa-jcs-2022",
"proofPurpose": "assertionMethod",
}

TEST_DID_DOCUMENT = DidDocument(
context=['https://www.w3.org/ns/did/v1'],
id=TEST_DID
).model_dump()
context=["https://www.w3.org/ns/did/v1"], id=TEST_DID
).model_dump()
20 changes: 8 additions & 12 deletions server/tests/signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,21 @@
from aries_askar.bindings import LocalKeyHandle
from tests.fixtures import TEST_SEED, TEST_PROOF_OPTIONS


def sign(document, options=TEST_PROOF_OPTIONS):
key = Key(LocalKeyHandle()).from_seed(KeyAlg.ED25519, TEST_SEED)
pub_key_multi = multibase.encode(
bytes.fromhex(
f"ed01{key.get_public_bytes().hex()}"
),
bytes.fromhex(f"ed01{key.get_public_bytes().hex()}"),
"base58btc",
)
options['verificationMethod'] = f'did:key:{pub_key_multi}#{pub_key_multi}'
options["verificationMethod"] = f"did:key:{pub_key_multi}#{pub_key_multi}"

hash_data = (
sha256(canonicaljson.encode_canonical_json(options)).digest()
+ sha256(canonicaljson.encode_canonical_json(document)).digest()
)

proof = options.copy()
proof["proofValue"] = multibase.encode(
key.sign_message(hash_data), "base58btc"
)

return document | {'proof': proof}

proof["proofValue"] = multibase.encode(key.sign_message(hash_data), "base58btc")

return document | {"proof": proof}
Loading

0 comments on commit b39fa27

Please sign in to comment.