Add foundational components for EAR generation (JWT creation and signing) and CI setup#12
Conversation
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
|
@THS-on @thomas-fossati @setrofim can you please review the final to_ and from_ methods continuation of #8 (comment) import json
from abc import ABC
from typing import Any, ClassVar, Dict, Tuple, Type, TypeVar, Union, get_args
T = TypeVar("T", bound="BaseJCSerializable")
def to_data(value: Any, keys_as_int=False) -> Any:
if hasattr(value, "to_data"):
return value.to_data(keys_as_int)
if hasattr(value, "items"): # dict-like
return {
to_data(k, keys_as_int): to_data(v, keys_as_int) for k, v in value.items()
}
if hasattr(value, "__iter__") and not isinstance(value, str): # list-like
return [to_data(v, keys_as_int) for v in value]
if hasattr(
value, "value"
): # custom classes that have value attr but don't have 'to_data'
return value.value # type: ignore[attr-defined]
# scalar and no to_data(), so assume serializable as-is
return value
class BaseJCSerializable(ABC):
jc_map: ClassVar[Dict[str, Tuple[int, str]]]
def to_data(self, keys_as_int=False) -> Dict[Union[str, int], Any]:
return {
(int_key if keys_as_int else str_key): to_data(
getattr(self, attr), keys_as_int
)
for attr, (int_key, str_key) in self.jc_map.items()
}
@classmethod
def from_data(cls: Type[T], data: dict, keys_as_int=False) -> T:
if keys_as_int:
index = 0
else:
index = 1
init_kwargs = {}
reverse_map = {v[index]: k for k, v in cls.jc_map.items()}
for key, value in data.items():
if key not in reverse_map:
continue
attr = reverse_map[key]
field_type = getattr(cls, "__annotations__", {}).get(attr)
if field_type is None:
continue
args = get_args(field_type)
if hasattr(field_type, "from_data"):
# Direct object
init_kwargs[attr] = field_type.from_data(value, keys_as_int=keys_as_int)
elif hasattr(field_type, "items") and hasattr(args[1], "from_data"):
# Dict[str | int, CustomClass]
init_kwargs[attr] = {
k: args[1].from_data(v, keys_as_int=keys_as_int)
for k, v in value.items()
}
elif args:
# custom classes that dont have 'from_data'
init_kwargs[attr] = args[0](value)
else:
init_kwargs[attr] = field_type(value)
return cls(**init_kwargs)
def to_dict(self) -> Dict[str, Any]:
# default str_keys
return self.to_data() # type: ignore[return-value] # pyright: ignore[reportGeneralTypeIssues] # noqa: E501 # pylint: disable=line-too-long
def to_int_keys(self) -> Dict[Union[str, int], Any]:
return self.to_data(keys_as_int=True)
@classmethod
def from_dict(cls: Type[T], data: Dict[str, Any]) -> T:
return cls.from_data(data)
@classmethod
def from_int_keys(cls: Type[T], data: Dict[int, Any]) -> T:
return cls.from_data(data, keys_as_int=True)
@classmethod
def from_json(cls, json_str: str):
return cls.from_dict(json.loads(json_str))
def to_json(self):
return json.dumps(self.to_data()) |
This are objects defined by us, right? So just explicitly check the type
In that case it is probably better to just do: Though this have also some caveats:
Also here can you explicitly for the types you expect?
Might be worth it to use a namedtuple instead, so that we are not doing random indices.
Same notes as above, check for types explicitly.
@setrofim can you take also a look? |
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
done |
initially I did the same but as suggested by Sergie here (point 4, #8 (comment)) I tried implementing duck-typing |
setrofim
left a comment
There was a problem hiding this comment.
JWT/JSON claim names need to be aligned with the corresponding spec (either the EAR draft or the EAT RFC). Apart from that, looks good.
src/claims.py
Outdated
| jc_map = { | ||
| "profile": KeyMapping(265, "profile"), | ||
| "issued_at": KeyMapping(6, "issued_at"), | ||
| "verifier_id": KeyMapping(1004, "verifier_id"), |
There was a problem hiding this comment.
The string name should be "ear.verifier-id". Please note that JSON field names are defined by draft-fv-rats-ear (see "JWT Claim Name" entry). The same goes for other claim names as well -- please make sure they align with the spec.
src/claims.py
Outdated
| } | ||
| # https://www.ietf.org/archive/id/draft-ietf-rats-eat-31.html#section-7.2.4 | ||
| jc_map = { | ||
| "profile": KeyMapping(265, "profile"), |
There was a problem hiding this comment.
The string name should be "eat_profile". This is a standard [EAT claim])https://www.rfc-editor.org/rfc/rfc9711.html#name-eat_profile-eat-profile-claim). The same goes for other claims as well please check you're using the correct name.
|
There are still some field names that are wrong -- please check all JSON field names against the corresponding specs, not just the ones I specifically highlighted (e.g. "issued_at" is still wrong). |
oh, totally missed it, will update |
d018be2 to
34ab6e3
Compare
done, now constructed EAR will look like this data = {
"eat_profile": "test_profile",
"iat": 1234567890,
"ear.verifier-id": {"developer": "Acme Inc.", "build": "v1"},
"submods": {
"submod1": {
"ear.trustworthiness-vector": {
"instance-identity": TRUSTWORTHY_INSTANCE_CLAIM.value,
"configuration": APPROVED_CONFIG_CLAIM.value,
"executables": APPROVED_RUNTIME_CLAIM.value,
"file-system": APPROVED_FILES_CLAIM.value,
"hardware": GENUINE_HARDWARE_CLAIM.value,
"runtime-opaque": ENCRYPTED_MEMORY_RUNTIME_CLAIM.value,
"storage-opaque": HW_KEYS_ENCRYPTED_SECRETS_CLAIM.value,
"sourced-data": TRUSTED_SOURCES_CLAIM.value,
},
"ear.status": TRUST_TIER_AFFIRMING.value,
}
},
} |
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
34ab6e3 to
3e6eb74
Compare
This PR introduces the foundational components required for EAT Attestation Results (EAR) generation. It includes:
AttestationResult,TrustVector,TrustClaim,VerifierID,trust_tierandsubmodsAttestationResultclassto_andfrom_methods)Continuation of reviews from #8