Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat/documentation] docs and refactoring for sd_jwt, storage, tools, trust and x509 #207

Merged
merged 101 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from 100 commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
c49bdfb
Merge pull request #104 from italia/dev
Sep 9, 2023
c6a81ca
Merge pull request #134 from italia/dev
Oct 9, 2023
70f50fa
Merge branch 'main' of https://github.com/italia/eudi-wallet-it-satos…
PascalDR Oct 16, 2023
46ff77f
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-satosa…
PascalDR Nov 22, 2023
56feaa5
feat: added policy apply on metadata
PascalDR Nov 23, 2023
1ea7bac
test: added intial tests for TrustEvaluationHelper
PascalDR Nov 23, 2023
47fd884
fix: fixed validation issues
PascalDR Nov 23, 2023
bd36786
feat: implemented method add_trust_attestation_metadata
PascalDR Nov 23, 2023
aa0133f
test: added test for add_trust_attestation_metadata
PascalDR Nov 23, 2023
4e44ecf
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-satosa…
PascalDR Nov 23, 2023
f512f59
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-satosa…
PascalDR Nov 27, 2023
1d3fa1b
fix: added metadata association by metadata_type field
PascalDR Nov 27, 2023
d90974c
fix: minor fix to test for add_trust_attestation_metadata's data type
PascalDR Nov 27, 2023
850d432
chore: renamed test file
PascalDR Nov 27, 2023
c062b35
chore: Removed comment
PascalDR Nov 27, 2023
bf3843c
fix: fixed x509 verification exception handling
PascalDR Nov 27, 2023
5a74ea0
chore: fix typo
PascalDR Nov 27, 2023
daeb343
fix: merged federation and metadata policy implementation
PascalDR Nov 29, 2023
f94b063
test: adapted tests
PascalDR Nov 29, 2023
67a4d12
feat: added final_metadata property
PascalDR Nov 29, 2023
059e94b
feat: added chain discovery plus refactoring
PascalDR Nov 29, 2023
5782f15
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-satosa…
PascalDR Nov 29, 2023
b807998
docs: documented file class and functions
PascalDR Dec 4, 2023
62edae6
fix: fixed trust_anchor_entity_conf handling
PascalDR Dec 4, 2023
9243e61
docs: documented trust_chain_builder.py
PascalDR Dec 4, 2023
24a8782
fix: moved implementation of get_http_url in utils.py
PascalDR Dec 5, 2023
29a876e
fix: fixed response handling
PascalDR Dec 5, 2023
1de5916
docs: documented file class and function plus refactoring
PascalDR Dec 5, 2023
7d5a273
docs: documented file __init__.py
PascalDR Dec 6, 2023
baaede8
docs: added docs for http_client.py
PascalDR Dec 6, 2023
d5758bd
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-satosa…
PascalDR Dec 6, 2023
2d04f1c
docs: documented the content of __init__.py
PascalDR Dec 11, 2023
0f61235
docs: documented contento of __init__.py
PascalDR Dec 11, 2023
1fef461
fix: method name refactoring
PascalDR Dec 11, 2023
f3fae08
fix: added exception
PascalDR Dec 11, 2023
d9a8f3e
fix: refactored method find_jwk
PascalDR Dec 11, 2023
eb0dfda
docs: fixed documentation
PascalDR Dec 11, 2023
46ac8bb
fix: refactoring
PascalDR Dec 11, 2023
3559531
docs: documented content of utils.py
PascalDR Dec 11, 2023
f7a85cc
docs: documented __init__.py content
PascalDR Dec 11, 2023
3d40222
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-satosa…
PascalDR Dec 11, 2023
f221ac1
fix: Resolved todo (what if the credential is not a JWT?)
PascalDR Dec 13, 2023
052262a
feat: implemented is_jwe_format and is_jws_format
PascalDR Dec 13, 2023
2711ba8
test: amplied test
PascalDR Dec 13, 2023
8a99ab1
fix: refactored code
PascalDR Dec 13, 2023
9b54e93
feat: resolved todo (detect if it is encrypted otherwise)
PascalDR Dec 13, 2023
2c3dc7d
fix: code refactoring
PascalDR Dec 13, 2023
61707f1
docs: documented content of direct_post_response.py
PascalDR Dec 13, 2023
084d1f2
fix: amplied error messages
PascalDR Dec 13, 2023
2ab4002
feat: resolved todo (automatic detection of the credential)
PascalDR Dec 13, 2023
2c2c80e
docs: amplied the documentation
PascalDR Dec 13, 2023
ebbf8a4
fix: refactored code
PascalDR Dec 13, 2023
7543752
fix: added dependency
PascalDR Dec 13, 2023
b8a9b27
docs: documented content of vp_sd_jwt.py
PascalDR Dec 13, 2023
91c5952
fix: refactored code
PascalDR Dec 13, 2023
b89312a
docs: documented content of vp.py
PascalDR Dec 13, 2023
c2cced2
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-satosa…
PascalDR Dec 13, 2023
86ebd73
fix: refactoring for better redability
PascalDR Dec 14, 2023
289bb3f
fix: redability fix
PascalDR Dec 14, 2023
b5b65ef
feat: added methods for handling credential's JWKs
PascalDR Dec 14, 2023
94f28b9
fix: fixed signatures
PascalDR Dec 14, 2023
7592b97
test: fixed test
PascalDR Dec 14, 2023
2bc8494
docs: documented the content of backend.py
PascalDR Dec 15, 2023
fde3b68
docs: documented code of dpop.py
PascalDR Dec 15, 2023
0109d7d
feat: created class BaseHTTPErrorHandler
PascalDR Dec 15, 2023
edbf46d
feat: created class BaseLogger
PascalDR Dec 15, 2023
b64e3a1
chore: removed unused implementation
PascalDR Dec 15, 2023
c6e4695
fix: code refactoring
PascalDR Dec 15, 2023
a3b6bd0
docs: added doc for _serialize_error
PascalDR Dec 15, 2023
0b8a933
docs: documented HTTPError and EmptyHTTPError
PascalDR Dec 15, 2023
14487a2
docs: fixed doc
PascalDR Dec 15, 2023
d5d4a5b
docs: documented content of html_template.py
PascalDR Dec 15, 2023
5905212
docs: documented content of response.py
PascalDR Dec 15, 2023
380df4f
docs: documented content of trust.py
PascalDR Dec 15, 2023
900edc3
fix: fixed signature
PascalDR Dec 15, 2023
e34e4bd
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-satosa…
PascalDR Dec 15, 2023
b8e929a
fix: fixed message passing
PascalDR Dec 15, 2023
90dcf63
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-satosa…
PascalDR Dec 19, 2023
97e0244
docs: documented content of __init__.py
PascalDR Dec 19, 2023
0247bc6
feat: added specialized classes for JWK
PascalDR Dec 20, 2023
93489ed
feat: added error type
PascalDR Dec 20, 2023
6b6ea9f
chore: moved file base_logger.py
PascalDR Dec 20, 2023
0853b82
chore: fixed import
PascalDR Dec 20, 2023
7112f4e
chore: fixed import
PascalDR Dec 20, 2023
516cc95
feat: added BaseDB class and it's documentation
PascalDR Dec 20, 2023
989fb7b
docs: documented content of base_cache.py
PascalDR Dec 20, 2023
df983ec
feat: added inheritance with BaseDB
PascalDR Dec 20, 2023
8dfd086
feat: added inheritance of BaseDB
PascalDR Dec 20, 2023
872ac11
docs: documented content of base_storage.py
PascalDR Dec 20, 2023
7a6c1ea
feat: documentation and refactoring
PascalDR Dec 20, 2023
3c084a5
feat: documentation and refactoring
PascalDR Dec 20, 2023
3a741d9
fix: varius minor fixs
PascalDR Dec 20, 2023
f766608
docs: documented content of mobile.py
PascalDR Dec 21, 2023
693acfc
docs: documented content of schema_utils.py
PascalDR Dec 21, 2023
77e0bcd
docs: documented content of utils.py
PascalDR Dec 21, 2023
faf57de
docs: documented content of trust_anchors.py
PascalDR Dec 21, 2023
f7faa9e
docs: documented content of trust_chain.py
PascalDR Dec 21, 2023
2427dce
docs: documented content of verify.py
PascalDR Dec 21, 2023
3b43529
docs: fixed docs
PascalDR Dec 21, 2023
a573679
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-satosa…
PascalDR Dec 21, 2023
fc4f7d9
fix: fixed functions name
PascalDR Dec 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions pyeudiw/jwk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import json
from typing import Union

Expand All @@ -6,14 +8,14 @@
from cryptojwt.jwk.jwk import key_from_jwk_dict
from cryptojwt.jwk.rsa import new_rsa_key

from .exceptions import InvalidKid, KidNotFoundError
from .exceptions import InvalidKid, KidNotFoundError, InvalidJwk

KEY_TYPES_FUNC = dict(
EC=new_ec_key,
RSA=new_rsa_key
)

class JWK():
class JWK:
"""
The class representing a JWK istance
"""
Expand Down Expand Up @@ -117,6 +119,34 @@ def as_dict(self) -> dict:
def __repr__(self):
# private part!
return self.as_json()

class RSAJWK(JWK):
def __init__(self, key: dict | None = None, hash_func: str = "SHA-256") -> None:
super().__init__(key, "RSA", hash_func, None)

class ECJWK(JWK):
def __init__(self, key: dict | None = None, hash_func: str = "SHA-256", ec_crv: str = "P-256") -> None:
super().__init__(key, "EC", hash_func, ec_crv)

def jwk_form_dict(key: dict, hash_func: str = "SHA-256") -> RSAJWK | ECJWK:
"""
Returns a JWK instance from a dict.

:param key: a dict that represents the key.
:type key: dict

:returns: a JWK instance.
:rtype: JWK
"""
_kty = key.get('kty', None)

if _kty == None or _kty not in ['EC', 'RSA']:
raise InvalidJwk("Invalid JWK")
elif _kty == "RSA":
return RSAJWK(key, hash_func)
else:
ec_crv = key.get('crv', "P-256")
return ECJWK(key, hash_func, ec_crv)

def find_jwk(kid: str, jwks: list[dict], as_dict: bool=True) -> dict | JWK:
"""
Expand Down
3 changes: 3 additions & 0 deletions pyeudiw/jwk/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ class InvalidKid(Exception):

class JwkError(Exception):
pass

class InvalidJwk(Exception):
pass
16 changes: 8 additions & 8 deletions pyeudiw/satosa/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

from .exceptions import HTTPError
from .base_http_error_handler import BaseHTTPErrorHandler
from .base_logger import BaseLogger
from pyeudiw.tools.base_logger import BaseLogger

class OpenID4VPBackend(BackendModule, BackendTrust, BackendDPoP, BaseHTTPErrorHandler, BaseLogger):
"""
Expand Down Expand Up @@ -152,8 +152,8 @@ def start_auth(self, context: Context, internal_request) -> Response:

def pre_request_endpoint(self, context: Context, internal_request, **kwargs) -> Response:
"""
This endpoint is called by the frontend before calling the request endpoint.
It initializes the session and returns the request_uri to be used by the frontend.
This endpoint is called by the User-Agent/Wallet Instance before calling the request endpoint.
It initializes the session and returns the request_uri to be used by the User-Agent/Wallet Instance.

:type context: the context of current request
:param context: the request context
Expand Down Expand Up @@ -215,12 +215,12 @@ def pre_request_endpoint(self, context: Context, internal_request, **kwargs) ->

def redirect_endpoint(self, context: Context, *args: tuple) -> Redirect | JsonResponse:
PascalDR marked this conversation as resolved.
Show resolved Hide resolved
"""
This endpoint is called by the frontend after the user has been authenticated.
This endpoint is called by the User-Agent/Wallet Instance after the user has been authenticated.

:type context: the context of current request
:param context: the request context

:return: a redirect to the frontend, if is in same device flow, or a json response if is in cross device flow.
:return: a redirect to the User-Agent/Wallet Instance, if is in same device flow, or a json response if is in cross device flow.
:rtype: Redirect | JsonResponse
"""

Expand Down Expand Up @@ -393,7 +393,7 @@ def redirect_endpoint(self, context: Context, *args: tuple) -> Redirect | JsonRe

def request_endpoint(self, context: Context, *args) -> JsonResponse:
"""
This endpoint is called by the frontend to retrieve the signed signed Request Object.
This endpoint is called by the User-Agent/Wallet Instance to retrieve the signed signed Request Object.

:type context: the context of current request
:param context: the request context
Expand Down Expand Up @@ -479,7 +479,7 @@ def request_endpoint(self, context: Context, *args) -> JsonResponse:

def get_response_endpoint(self, context: Context) -> Response:
"""
This endpoint is called by the frontend to retrieve the response of the authentication.
This endpoint is called by the User-Agent/Wallet Instance to retrieve the response of the authentication.

:param context: the request context
:type context: satosa.context.Context
Expand Down Expand Up @@ -529,7 +529,7 @@ def get_response_endpoint(self, context: Context) -> Response:

def status_endpoint(self, context: Context) -> JsonResponse:
"""
This endpoint is called by the frontend the url to the response endpoint to finalize the process.
This endpoint is called by the User-Agent/Wallet Instance the url to the response endpoint to finalize the process.

:param context: the request context
:type context: satosa.context.Context
Expand Down
2 changes: 1 addition & 1 deletion pyeudiw/satosa/base_http_error_handler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from satosa.context import Context
from .base_logger import BaseLogger
from pyeudiw.tools.base_logger import BaseLogger
from .exceptions import EmptyHTTPError
from pyeudiw.satosa.response import JsonResponse

Expand Down
2 changes: 1 addition & 1 deletion pyeudiw/satosa/dpop.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from satosa.context import Context
from pydantic import ValidationError

from .base_logger import BaseLogger
from pyeudiw.tools.base_logger import BaseLogger
from .base_http_error_handler import BaseHTTPErrorHandler

class BackendDPoP(BaseHTTPErrorHandler, BaseLogger):
Expand Down
2 changes: 1 addition & 1 deletion pyeudiw/satosa/trust.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from pyeudiw.trust import TrustEvaluationHelper
from pyeudiw.trust.trust_anchors import update_trust_anchors_ecs

from .base_logger import BaseLogger
from pyeudiw.tools.base_logger import BaseLogger

class BackendTrust(BaseLogger):
"""
Expand Down
172 changes: 160 additions & 12 deletions pyeudiw/sd_jwt/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import cryptojwt
import json

from jwcrypto.common import base64url_encode
Expand All @@ -23,12 +22,45 @@
import jwcrypto

from typing import Any

from cryptojwt.jwk.rsa import RSAKey
from cryptojwt.jwk.ec import ECKey
from cryptography.hazmat.backends.openssl.rsa import _RSAPrivateKey

class TrustChainSDJWTIssuer(SDJWTIssuer):
def __init__(self, user_claims: Dict, issuer_key, holder_key=None, sign_alg=None, add_decoy_claims: bool = True, serialization_format: str = "compact", additional_headers: dict = {}):
"""
Class for issue SD-JWT of TrustChain.
"""
def __init__(
self,
user_claims: Dict[str, Any],
issuer_key: dict,
holder_key: dict | None = None,
sign_alg: str | None = None,
add_decoy_claims: bool = True,
serialization_format: str = "compact",
additional_headers: dict = {}
) -> None:
"""
Crate an istance of TrustChainSDJWTIssuer.

:param user_claims: the claims of the SD-JWT.
:type user_claims: dict
:param issuer_key: the issuer key.
:type issuer_key: dict
:param holder_key: the holder key.
:type holder_key: dict | None
:param sign_alg: the signing algorithm.
:type sign_alg: str | None
:param add_decoy_claims: if True add decoy claims.
:type add_decoy_claims: bool
:param serialization_format: the serialization format.
:type serialization_format: str
:param additional_headers: additional headers.
:type additional_headers: dict
"""

self.additional_headers = additional_headers
sign_alg = DEFAULT_SIG_KTY_MAP[issuer_key.kty]
sign_alg = sign_alg if sign_alg else DEFAULT_SIG_KTY_MAP[issuer_key.kty]

super().__init__(
user_claims,
Expand All @@ -40,6 +72,9 @@ def __init__(self, user_claims: Dict, issuer_key, holder_key=None, sign_alg=None
)

def _create_signed_jws(self):
"""
Creates the signed JWS.
"""
self.sd_jwt = JWS(payload=dumps(self.sd_jwt_payload))

_protected_headers = {"alg": self._sign_alg}
Expand Down Expand Up @@ -67,9 +102,19 @@ def _create_signed_jws(self):
self.serialized_sd_jwt = dumps(jws_content)


def _serialize_key(key, **kwargs):
def _serialize_key(
key: RSAKey | ECKey | JWK | dict,
**kwargs
) -> dict:
"""
Serialize a key into dict.

:param key: the key to serialize.
:type key: RSAKey | ECKey | JWK | dict

if isinstance(key, cryptojwt.jwk.rsa.RSAKey):
:returns: the serialized key into a dict.
"""
if isinstance(key, RSAKey) or isinstance(key, ECKey):
key = key.serialize()
elif isinstance(key, JWK):
key = key.as_dict()
Expand All @@ -80,7 +125,19 @@ def _serialize_key(key, **kwargs):
return key


def pk_encode_int(i, bit_size=None):
def pk_encode_int(i: str, bit_size: int = None) -> str:
"""
Encode an integer as a base64url string with padding.

:param i: the integer to encode.
:type i: str
:param bit_size: the bit size of the integer.
:type bit_size: int

:returns: the encoded integer.
:rtype: str
"""

extend = 0
if bit_size is not None:
extend = ((bit_size + 7) // 8) * 2
Expand All @@ -93,7 +150,22 @@ def pk_encode_int(i, bit_size=None):
return base64url_encode(unhexlify(extend * '0' + hexi))


def import_pyca_pri_rsa(key, **params):
def import_pyca_pri_rsa(key: _RSAPrivateKey, **params) -> jwcrypto.jwk.JWK:
"""
Import a private RSA key from a PyCA object.

:param key: the key to import.
:type key: RSAKey | ECKey

:raises ValueError: if the key is not a PyCA RSAKey object.

:returns: the imported key.
:rtype: RSAKey
"""

if not isinstance(key, _RSAPrivateKey):
raise ValueError("key must be a ssl RSAPrivateKey object")

pn = key.private_numbers()
params.update(
kty='RSA',
Expand Down Expand Up @@ -129,7 +201,19 @@ def import_ec(key, **params):
)
return jwcrypto.jwk.JWK(**params)

def _adapt_keys(issuer_key: JWK, holder_key: JWK):
def _adapt_keys(issuer_key: JWK, holder_key: JWK) -> dict:
"""
Adapt the keys to the SD-JWT library.

:param issuer_key: the issuer key.
:type issuer_key: JWK
:param holder_key: the holder key.
:type holder_key: JWK

:returns: the adapted keys as a dict.
:rtype: dict
"""

# _iss_key = issuer_key.key.serialize(private=True)
# _iss_key['key_ops'] = 'sign'

Expand All @@ -152,11 +236,45 @@ def _adapt_keys(issuer_key: JWK, holder_key: JWK):
)


def load_specification_from_yaml_string(yaml_specification: str):
def load_specification_from_yaml_string(yaml_specification: str) -> dict:
"""
Load a specification from a yaml string.

:param yaml_specification: the yaml string.
:type yaml_specification: str

:returns: the specification as a dict.
:rtype: dict
"""

return _yaml_load_specification(StringIO(yaml_specification))


def issue_sd_jwt(specification: dict, settings: dict, issuer_key: JWK, holder_key: JWK, trust_chain: list[str] | None = None) -> str:
def issue_sd_jwt(
specification: Dict[str, Any],
settings: dict,
issuer_key: JWK,
holder_key: JWK,
trust_chain: list[str] | None = None
) -> str:
"""
Issue a SD-JWT.

:param specification: the specification of the SD-JWT.
:type specification: Dict[str, Any]
:param settings: the settings of the SD-JWT.
:type settings: dict
:param issuer_key: the issuer key.
:type issuer_key: JWK
:param holder_key: the holder key.
:type holder_key: JWK
:param trust_chain: the trust chain.
:type trust_chain: list[str] | None

:returns: the issued SD-JWT.
:rtype: str
"""

claims = {
"iss": settings["issuer"],
"iat": iat_now(),
Expand All @@ -180,7 +298,23 @@ def issue_sd_jwt(specification: dict, settings: dict, issuer_key: JWK, holder_ke
return {"jws": sdjwt_at_issuer.serialized_sd_jwt, "issuance": sdjwt_at_issuer.sd_jwt_issuance}


def _cb_get_issuer_key(issuer: str, settings: dict, adapted_keys: dict, *args, **kwargs):
def _cb_get_issuer_key(issuer: str, settings: dict, adapted_keys: dict, *args, **kwargs) -> JWK:
"""
Helper function for get the issuer key.

:param issuer: the issuer.
:type issuer: str
:param settings: the settings of SD-JWT.
:type settings: dict
:param adapted_keys: the adapted keys.
:type adapted_keys: dict

:raises Exception: if the issuer is unknown.

:returns: the issuer key.
:rtype: JWK
"""

if issuer == settings["issuer"]:
return adapted_keys["issuer_public_key"]
else:
Expand All @@ -193,6 +327,20 @@ def verify_sd_jwt(
holder_key: JWK,
settings: dict = {'key_binding': True}
) -> (list | dict | Any):
"""
Verify a SD-JWT.

:param sd_jwt_presentation: the SD-JWT to verify.
:type sd_jwt_presentation: str
:param issuer_key: the issuer key.
:type issuer_key: JWK
:param holder_key: the holder key.
:type holder_key: JWK
:param settings: the settings of SD-JWT.

:returns: the verified payload.
:rtype: list | dict | Any
"""

settings.update(
{
Expand Down
Loading
Loading