Skip to content

Commit

Permalink
📝 Swagger updates wallet/jws (#818)
Browse files Browse the repository at this point in the history
* update docstrings

* formatting

* lines too long

* update docstrings

* update model to enforce payload and enforce one of did/verification method

* formatting

* update docstrings

* fix typo

* updated docstrings sd-jws/jws

* formatting

* add example payload

* formatting move string around

* add example payload

* shorten string

* fix typo

* some minor edits

* minor edits to sd_jws

* typo

* jes unit tests

* add sd_jwt unit tests

* 🎨

* 🎨

* 🎨

* test jws model

* 🎨

* edits

* 🎨

* 🎨 Updated docstrings

* 🎨

---------

Co-authored-by: ff137 <[email protected]>
  • Loading branch information
cl0ete and ff137 authored Nov 21, 2024
1 parent 862518b commit e587130
Show file tree
Hide file tree
Showing 8 changed files with 729 additions and 20 deletions.
16 changes: 14 additions & 2 deletions app/models/jws.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class JWSCreateRequest(BaseModel):
None, examples=["did:key:z6MkjCjxuTXxVPWS9JYj2ZiKtKvSS1srC6kBRes4WCB2mSWq"]
)
headers: Dict = Field(default={})
payload: Dict = Field(default={})
payload: Dict = Field(description="Payload to sign")
verification_method: Optional[str] = Field(
None,
description="Information used for proof verification",
Expand All @@ -26,8 +26,20 @@ def check_at_least_one_field_is_populated(cls, values):
did, verification_method = values.get("did"), values.get("verification_method")
if not did and not verification_method:
raise CloudApiValueError(
"At least one of `did` or `verification_method` must be populated."
"One of `did` or `verification_method` must be populated."
)
if did and verification_method:
raise CloudApiValueError(
"Only one of `did` or `verification_method` can be populated."
)
return values

@model_validator(mode="before")
@classmethod
def check_payload_is_populated(cls, values):
payload = values.get("payload")
if not payload:
raise CloudApiValueError("`payload` must be populated.")
return values


Expand Down
99 changes: 91 additions & 8 deletions app/routes/wallet/jws.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,72 @@
"/sign",
response_model=JWSCreateResponse,
summary="Sign JWS",
description="""
Sign JSON Web Signature (JWS)
See https://www.rfc-editor.org/rfc/rfc7515.html for the JWS spec.""",
)
async def sign_jws(
body: JWSCreateRequest,
auth: AcaPyAuth = Depends(acapy_auth_from_header),
) -> JWSCreateResponse:
"""
Sign a JSON Web Signature (JWS).
---
This endpoint allows users to sign a JSON payload, creating a JWS,
using either a DID or a specific verification method.
**Usage:**
- **DID-Based Signing:** Provide the `did` field with a valid DID.
The Aries agent will automatically select the appropriate verification key associated with the DID.
- **Verification Method-Based Signing:** Provide the `verification_method` field with a specific verification method
(DID with a verkey) to explicitly specify which key to use for signing.
**Notes:**
- If the issuer uses a `did:sov` DID, ensure that the DID is public.
- The `header` field is optional. While you can specify custom headers, the `typ`, `alg`,
and `kid` fields are automatically populated by the Aries agent based on the signing method.
Example request body:
```json
{
"did": "did:sov:WWMjrBJkUzz9suEtwKxmiY",
"payload": {
"credential_subject": "reference_to_holder",
"name": "Alice",
"surname": "Demo"
}
}
```
**OR**
```json
{
"payload": {
"subject": "reference_to_holder",
"name": "Alice",
"surname": "Demo"
},
"verification_method": "did:key:z6Mkprf81ujG1n48n5LMD...M6S3#z6Mkprf81ujG1n48n5LMDaxyCLLFrnqCRBPhkTWsPfA8M6S3"
}
```
Request Body:
---
JWSCreateRequest:
`did` (str, optional): The DID to sign the JWS with.
`verification_method` (str, optional): The verification method (DID with verkey) to use for signing.
`payload` (dict): The JSON payload to be signed.
`headers` (dict, optional): Custom headers for the JWS.
Response:
---
JWSCreateResponse:
`jws` (str): The resulting JWS string representing the signed JSON Web Signature.
**References:**
- [JSON Web Signature (JWS) Specification](https://www.rfc-editor.org/rfc/rfc7515.html)
"""
bound_logger = logger.bind(
# Do not log payload:
body=body.model_dump(exclude="payload")
Expand Down Expand Up @@ -64,15 +121,41 @@ async def sign_jws(
"/verify",
response_model=JWSVerifyResponse,
summary="Verify JWS",
description="""
Verify JSON Web Signature (JWS)
See https://www.rfc-editor.org/rfc/rfc7515.html for the JWS spec.""",
)
async def verify_jws(
body: JWSVerifyRequest,
auth: AcaPyAuth = Depends(acapy_auth_from_header),
) -> JWSVerifyResponse:
"""
Verify a JSON Web Signature (JWS)
---
This endpoint allows users to verify the authenticity and integrity of a JWS string previously generated
by the /sign endpoint. It decodes the JWS to retrieve the payload and headers and assesses its validity.
Request Body:
---
JWSVerifyRequest: The JWS to verify.
jws: str
Returns:
---
JWSVerifyResponse
payload: dict:
The payload of the JWS.
headers: dict:
The headers of the JWS.
kid: str:
The key id of the signer.
valid: bool:
Whether the JWS is valid.
error: str:
The error message if the JWS is invalid.
**References:**
- [JSON Web Signature (JWS) Specification](https://www.rfc-editor.org/rfc/rfc7515.html)
"""
bound_logger = logger.bind(body=body)
bound_logger.debug("POST request received: Verify JWS")

Expand Down
136 changes: 126 additions & 10 deletions app/routes/wallet/sd_jws.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,91 @@
"/sign",
response_model=SDJWSCreateResponse,
summary="Sign SD-JWS",
description="""
Sign Select Disclosure for JWS (SD-JWS)
See https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html for the SD-JWT / SD-JWS spec.
""",
)
async def sign_sd_jws(
body: SDJWSCreateRequest,
auth: AcaPyAuth = Depends(acapy_auth_from_header),
) -> SDJWSCreateResponse:
"""
Sign a Selective Disclosure JSON Web Signature (SD-JWS).
---
This endpoint allows users to create a Selective Disclosure JSON Web Signature (SD-JWS).
The SD-JWS enables the selective disclosure of specific attributes to a verifier while keeping others confidential.
**Usage:**
- **DID-Based Signing:** Provide the `did` field with a valid DID.
The Aries agent will automatically select the appropriate verification key associated with the DID.
- **Verification Method-Based Signing:** Provide the `verification_method` field with a specific verification method
(DID with verkey) to explicitly specify which key to use for signing.
**Notes:**
- If the issuer uses a `did:sov` DID, ensure that the DID is public.
- The `headers` field is optional. Custom headers can be specified, but the `typ`, `alg`,
and `kid` fields are automatically populated by the Aries agent based on the signing method.
- The `non_sd_list` field specifies attributes that are **not** selectively disclosed.
Attributes listed here will always be included in the SD-JWS.
**Non-Selective Disclosure (`non_sd_list`):**
- To exclude list elements:
- Use the format `"<attribute_name>[start:end]"` where `start` and `end` define the range
(e.g., `"nationalities[1:3]"`).
- To exclude specific dictionary attributes:
- Use the format `"<dictionary_name>.<attribute_name>"` (e.g., `"address.street_address"`).
**Example Request Body:**
```json
{
"did": "did:sov:39TXHazGAYif5FUFCjQhYX",
"payload": {
"credential_subject": "reference_to_holder",
"given_name": "John",
"family_name": "Doe",
"email": "[email protected]",
"phone_number": "+27-123-4567",
"nationalities": ["a","b","c","d"],
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "Anywhere",
"country": "ZA"
},
"birthdate": "1940-01-01"
},
"non_sd_list": [
"given_name",
"address",
"address.street_address",
"nationalities",
"nationalities[1:3]"
]
}
```
Request Body:
---
SDJWSCreateRequest:
`did` (str, optional): The DID to sign the SD-JWS with.
`verification_method` (str, optional): The verification method (DID with verkey) to use for signing.
`payload` (dict): The JSON payload to be signed.
`headers` (dict, optional): Custom headers for the SD-JWS.
`non_sd_list` (List[str], optional): List of attributes excluded from selective disclosure.
Response:
---
SDJWSCreateResponse:
`sd_jws` (str): The resulting SD-JWS string concatenated with the necessary disclosures in the format
`<Issuer-signed JWS>~<Disclosure 1>~<Disclosure 2>~...~<Disclosure N>`.
**References:**
- [Selective Disclosure JSON Web Token (SD-JWT)
Specification](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html)
"""
bound_logger = logger.bind(
# Do not log payload:
body=body.model_dump(exclude="payload")
Expand Down Expand Up @@ -65,16 +140,57 @@ async def sign_sd_jws(
"/verify",
response_model=SDJWSVerifyResponse,
summary="Verify SD-JWS",
description="""
Verify Select Disclosure for JWS (SD-JWS)
See https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html for the SD-JWT / SD-JWS spec.
""",
)
async def verify_sd_jws(
body: SDJWSVerifyRequest,
auth: AcaPyAuth = Depends(acapy_auth_from_header),
) -> SDJWSVerifyResponse:
"""
Verify a Selective Disclosure JSON Web Signature (SD-JWS).
---
This endpoint allows users to verify the authenticity and integrity of a Selective Disclosure
JSON Web Signature (SD-JWS). It decodes the SD-JWS to retrieve the payload and headers,
assesses its validity, and processes the disclosures.
**Usage:**
- Submit the SD-JWS string concatenated with the necessary disclosures to this endpoint.
- The format should be: `<Issuer-signed JWS>~<Disclosure 1>~<Disclosure 2>~...~<Disclosure N>`.
- The holder provides the SD-JWS along with the required disclosures based on the verifier's request.
**Notes:**
- Only the disclosures relevant to the verifier's request needs to be provided.
Other disclosures can remain confidential.
**Example Request Body:**
```json
{
"sd_jws": "<Issuer-signed JWS>~<Disclosure 1>~<Disclosure 2>~...~<Disclosure N>"
}
```
Request Body:
---
SDJWSVerifyRequest:
`sd_jws` (str): The concatenated SD-JWS and disclosures to verify and reveal.
Response:
---
SDJWSVerifyResponse:
`valid` (bool): Indicates whether the SD-JWS is valid.
`payload` (dict): The decoded payload of the SD-JWS.
`headers` (dict): The headers extracted from the SD-JWS.
`kid` (str): The Key ID of the signer.
`disclosed_attributes` (dict): The selectively disclosed attributes based on the provided disclosures.
`error` (str, optional): Error message if the SD-JWS verification fails.
**References:**
- [Selective Disclosure JSON Web Token (SD-JWT)
Specification](https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-07.html)
"""
bound_logger = logger.bind(body=body)
bound_logger.debug("POST request received: Verify SD-JWS")

Expand Down
32 changes: 32 additions & 0 deletions app/tests/models/test_jws.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pytest

from app.models.jws import JWSCreateRequest
from shared.exceptions.cloudapi_value_error import CloudApiValueError


def test_jws_create_request():
# no did or verification_method
with pytest.raises(CloudApiValueError) as exc:
JWSCreateRequest(payload={"test": "test_value"})

assert exc.value.detail == (
"One of `did` or `verification_method` must be populated."
)

# did and verification_method
with pytest.raises(CloudApiValueError) as exc:
JWSCreateRequest(
did="did:sov:AGguR4mc186Tw11KeWd4qq",
payload={"test": "test_value"},
verification_method="did:sov:AGguR4mc186Tw11KeWd4qq",
)

assert exc.value.detail == (
"Only one of `did` or `verification_method` can be populated."
)

# no payload
with pytest.raises(CloudApiValueError) as exc:
JWSCreateRequest(did="did:sov:AGguR4mc186Tw11KeWd4qq")

assert exc.value.detail == ("`payload` must be populated.")
Loading

0 comments on commit e587130

Please sign in to comment.