-
Notifications
You must be signed in to change notification settings - Fork 1
Add foundational components for EAR generation (JWT creation and signing) and CI setup #12
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
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
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