Skip to content

Commit 8023a33

Browse files
committed
Add trust_tier type for status alongside trust_vector
Signed-off-by: HarshvMahawar <hv062727@gmail.com>
1 parent a545eb6 commit 8023a33

File tree

7 files changed

+274
-29
lines changed

7 files changed

+274
-29
lines changed

src/claims.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@
22
from dataclasses import dataclass, field
33
from typing import Any, Dict
44

5+
from src.trust_tier import to_trust_tier
56
from src.trust_vector import TrustVector
67
from src.verifier_id import VerifierID
78

89

9-
# Represents the EAR Claims set that will be populated
1010
@dataclass
1111
class EARClaims:
1212
profile: str
1313
issued_at: int
1414
verifier_id: VerifierID
1515
submods: Dict[str, Dict[str, Any]] = field(default_factory=dict)
1616

17-
# Returns a python dictionary that will be used for serializing to JWT
1817
def to_dict(self) -> Dict[str, Any]:
1918
return {
2019
"eat_profile": self.profile,
@@ -23,7 +22,7 @@ def to_dict(self) -> Dict[str, Any]:
2322
"submods": {
2423
key: {
2524
"trust_vector": value["trust_vector"].to_dict(),
26-
"status": value["status"],
25+
"status": value["status"].value,
2726
}
2827
for key, value in self.submods.items()
2928
},
@@ -40,8 +39,8 @@ def from_dict(cls, data: Dict[str, Any]):
4039
verifier_id=VerifierID.from_dict(data.get("ear.verifier-id", {})),
4140
submods={
4241
key: {
43-
"trust_vector": TrustVector.from_dict(value["trust_vector"]), # noqa: E501
44-
"status": value["status"],
42+
"trust_vector": TrustVector.from_dict(value["trust_vector"]),
43+
"status": to_trust_tier(value["status"]),
4544
}
4645
for key, value in data.get("submods", {}).items()
4746
},

src/trust_tier.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from dataclasses import dataclass
2+
from typing import Any, Dict
3+
4+
5+
@dataclass(frozen=True)
6+
class TrustTier:
7+
value: int
8+
9+
10+
def to_trust_tier(value: Any) -> TrustTier:
11+
# Converts an integer or string to a TrustTier instance, defaulting to TrustTierNone on failure
12+
if isinstance(value, int):
13+
return IntToTrustTier.get(value, TrustTierNone)
14+
if isinstance(value, str):
15+
return StringToTrustTier.get(value, TrustTierNone)
16+
raise ValueError(f"Cannot convert {value} (type {type(value)}) to TrustTier")
17+
18+
19+
# Defining trust tiers
20+
TrustTierNone = TrustTier(0)
21+
TrustTierAffirming = TrustTier(2)
22+
TrustTierWarning = TrustTier(32)
23+
TrustTierContraindicated = TrustTier(96)
24+
25+
# Mapping from TrustTier to string representation
26+
TrustTierToString: Dict[TrustTier, str] = {
27+
TrustTierNone: "none",
28+
TrustTierAffirming: "affirming",
29+
TrustTierWarning: "warning",
30+
TrustTierContraindicated: "contraindicated",
31+
}
32+
33+
# Reverse mapping from string to TrustTier
34+
StringToTrustTier: Dict[str, TrustTier] = {v: k for k, v in TrustTierToString.items()}
35+
36+
# Mapping from integer value to TrustTier
37+
IntToTrustTier: Dict[int, TrustTier] = {
38+
TrustTierNone.value: TrustTierNone,
39+
TrustTierAffirming.value: TrustTierAffirming,
40+
TrustTierWarning.value: TrustTierWarning,
41+
TrustTierContraindicated.value: TrustTierContraindicated,
42+
}

src/validation.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from typing import Dict
2+
3+
from src.claims import EARClaims
4+
from src.trust_claims import TrustClaim
5+
from src.trust_vector import TrustVector
6+
from src.verifier_id import VerifierID
7+
8+
9+
class EARValidationError(Exception):
10+
"""Custom exception for validation errors in EARClaims"""
11+
12+
pass
13+
14+
15+
def validate_trust_claim(trust_claim: TrustClaim):
16+
# Validates a TrustClaim object
17+
if not isinstance(trust_claim.value, int) or not -128 <= trust_claim.value <= 127:
18+
raise EARValidationError(
19+
f"Invalid value in TrustClaim: {trust_claim.value}. Must be in range [-128, 127]"
20+
)
21+
if not isinstance(trust_claim.tag, str):
22+
raise EARValidationError("TrustClaim tag must be a string")
23+
if not isinstance(trust_claim.short, str):
24+
raise EARValidationError("TrustClaim short description must be a string")
25+
if not isinstance(trust_claim.long, str):
26+
raise EARValidationError("TrustClaim long description must be a string")
27+
28+
29+
def validate_trust_vector(trust_vector: TrustVector):
30+
# Validates a TrustVector object
31+
if not isinstance(trust_vector, TrustVector):
32+
raise EARValidationError("Invalid TrustVector object")
33+
34+
for claim in trust_vector.__dict__.values():
35+
if claim is not None:
36+
validate_trust_claim(claim)
37+
38+
39+
def validate_verifier_id(verifier_id: VerifierID):
40+
# Validates a VerifierID object
41+
if not isinstance(verifier_id, VerifierID):
42+
raise EARValidationError("Invalid VerifierID object")
43+
if not verifier_id.developer or not isinstance(verifier_id.developer, str):
44+
raise EARValidationError("VerifierID developer must be a non-empty string")
45+
if not verifier_id.build or not isinstance(verifier_id.build, str):
46+
raise EARValidationError("VerifierID build must be a non-empty string")
47+
48+
49+
def validate_ear_claims(ear_claims: EARClaims):
50+
# Validates an EARClaims object
51+
if not isinstance(ear_claims, EARClaims):
52+
raise EARValidationError("Invalid EARClaims object")
53+
if not isinstance(ear_claims.profile, str) or not ear_claims.profile:
54+
raise EARValidationError("EARClaims profile must be a non-empty string")
55+
if not isinstance(ear_claims.issued_at, int) or ear_claims.issued_at <= 0:
56+
raise EARValidationError("EARClaims issued_at must be a positive integer")
57+
58+
validate_verifier_id(ear_claims.verifier_id)
59+
60+
for submod, details in ear_claims.submods.items():
61+
if (
62+
not isinstance(details, Dict)
63+
or "trust_vector" not in details
64+
or "status" not in details
65+
):
66+
raise EARValidationError(
67+
f"Submodule {submod} must contain a valid trust_vector and status"
68+
)
69+
validate_trust_vector(details["trust_vector"])
70+
if not isinstance(details["status"], str):
71+
raise EARValidationError(f"Submodule {submod} status must be a string")
72+
73+
74+
def validate_all(ear_claims: EARClaims):
75+
# Runs all validation checks on the provided EARClaims object
76+
try:
77+
validate_ear_claims(ear_claims)
78+
print("EARClaims validation successful.")
79+
except EARValidationError as e:
80+
print(f"Validation failed: {e}")

tests/test_claims.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import pytest
22

33
from src.claims import EARClaims
4-
from src.trust_claims import (
5-
ApprovedConfigClaim,
6-
ApprovedFilesClaim,
7-
ApprovedRuntimeClaim,
8-
EncryptedMemoryRuntimeClaim,
9-
GenuineHardwareClaim,
10-
HwKeysEncryptedSecretsClaim,
11-
TrustedSourcesClaim,
12-
TrustworthyInstanceClaim,
13-
)
4+
from src.trust_claims import (ApprovedConfigClaim, ApprovedFilesClaim,
5+
ApprovedRuntimeClaim,
6+
EncryptedMemoryRuntimeClaim,
7+
GenuineHardwareClaim,
8+
HwKeysEncryptedSecretsClaim, TrustedSourcesClaim,
9+
TrustworthyInstanceClaim)
10+
from src.trust_tier import TrustTierAffirming
1411
from src.trust_vector import TrustVector
1512
from src.verifier_id import VerifierID
1613

@@ -33,7 +30,7 @@ def sample_ear_claims():
3330
storage_opaque=HwKeysEncryptedSecretsClaim,
3431
sourced_data=TrustedSourcesClaim,
3532
),
36-
"status": "affirming",
33+
"status": TrustTierAffirming,
3734
}
3835
},
3936
)
@@ -56,7 +53,7 @@ def test_ear_claims_to_dict(sample_ear_claims):
5653
"storage_opaque": HwKeysEncryptedSecretsClaim.to_dict(),
5754
"sourced_data": TrustedSourcesClaim.to_dict(),
5855
},
59-
"status": "affirming",
56+
"status": TrustTierAffirming.value,
6057
}
6158
},
6259
}
@@ -86,7 +83,7 @@ def test_ear_claims_from_dict():
8683
"storage_opaque": HwKeysEncryptedSecretsClaim.to_dict(),
8784
"sourced_data": TrustedSourcesClaim.to_dict(),
8885
},
89-
"status": "affirming",
86+
"status": TrustTierAffirming.value,
9087
}
9188
},
9289
}

tests/test_trust_tier.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import pytest
2+
3+
from src.trust_tier import (TrustTierAffirming, TrustTierContraindicated,
4+
TrustTierNone, TrustTierWarning, to_trust_tier)
5+
6+
7+
def test_to_trust_tier_valid_int():
8+
assert to_trust_tier(0) == TrustTierNone
9+
assert to_trust_tier(2) == TrustTierAffirming
10+
assert to_trust_tier(32) == TrustTierWarning
11+
assert to_trust_tier(96) == TrustTierContraindicated
12+
13+
14+
def test_to_trust_tier_valid_str():
15+
assert to_trust_tier("none") == TrustTierNone
16+
assert to_trust_tier("affirming") == TrustTierAffirming
17+
assert to_trust_tier("warning") == TrustTierWarning
18+
assert to_trust_tier("contraindicated") == TrustTierContraindicated
19+
20+
21+
def test_to_trust_tier_invalid_int():
22+
assert to_trust_tier(100) == TrustTierNone # Default fallback
23+
24+
25+
def test_to_trust_tier_invalid_str():
26+
assert to_trust_tier("invalid_string") == TrustTierNone # Default fallback
27+
28+
29+
def test_to_trust_tier_invalid_type():
30+
with pytest.raises(ValueError):
31+
to_trust_tier([1, 2, 3])
32+
33+
with pytest.raises(ValueError):
34+
to_trust_tier({"tier": "affirming"})

tests/test_trust_vector.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,11 @@
22

33
import pytest
44

5-
from src.trust_claims import (
6-
ApprovedFilesClaim,
7-
ApprovedRuntimeClaim,
8-
EncryptedMemoryRuntimeClaim,
9-
GenuineHardwareClaim,
10-
HwKeysEncryptedSecretsClaim,
11-
TrustedSourcesClaim,
12-
TrustworthyInstanceClaim,
13-
UnsafeConfigClaim,
14-
)
5+
from src.trust_claims import (ApprovedFilesClaim, ApprovedRuntimeClaim,
6+
EncryptedMemoryRuntimeClaim,
7+
GenuineHardwareClaim,
8+
HwKeysEncryptedSecretsClaim, TrustedSourcesClaim,
9+
TrustworthyInstanceClaim, UnsafeConfigClaim)
1510
from src.trust_vector import TrustVector
1611

1712

@@ -90,4 +85,6 @@ def test_trust_vector_from_cbor():
9085
7: TrustedSourcesClaim.to_dict(),
9186
}
9287
parsed_vector = TrustVector.from_cbor(cbor_data)
93-
assert parsed_vector.to_dict() == TrustVector.from_cbor(cbor_data).to_dict() # noqa: E501
88+
assert (
89+
parsed_vector.to_dict() == TrustVector.from_cbor(cbor_data).to_dict()
90+
) # noqa: E501

tests/test_validation.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import pytest
2+
3+
from src.claims import EARClaims
4+
from src.trust_claims import (TrustClaim, TrustworthyInstanceClaim,
5+
UnsafeConfigClaim)
6+
from src.trust_vector import TrustVector
7+
from src.validation import (EARValidationError, validate_ear_claims,
8+
validate_trust_claim, validate_trust_vector,
9+
validate_verifier_id)
10+
from src.verifier_id import VerifierID
11+
12+
13+
@pytest.fixture
14+
def valid_trust_claim():
15+
return TrustClaim(
16+
value=2,
17+
tag="approved_config",
18+
short="Approved",
19+
long="Configuration is approved.",
20+
)
21+
22+
23+
@pytest.fixture
24+
def valid_trust_vector():
25+
return TrustVector(
26+
instance_identity=TrustworthyInstanceClaim,
27+
configuration=UnsafeConfigClaim,
28+
)
29+
30+
31+
@pytest.fixture
32+
def valid_verifier_id():
33+
return VerifierID(developer="Acme Inc.", build="v1.0.0")
34+
35+
36+
@pytest.fixture
37+
def valid_ear_claims(valid_trust_vector, valid_verifier_id):
38+
return EARClaims(
39+
profile="test_profile",
40+
issued_at=1234567890,
41+
verifier_id=valid_verifier_id,
42+
submods={
43+
"submod1": {
44+
"trust_vector": valid_trust_vector,
45+
"status": "affirming",
46+
}
47+
},
48+
)
49+
50+
51+
def test_validate_trust_claim(valid_trust_claim):
52+
# Should not raise an error
53+
validate_trust_claim(valid_trust_claim)
54+
55+
56+
def test_validate_trust_claim_invalid():
57+
with pytest.raises(EARValidationError):
58+
validate_trust_claim(
59+
TrustClaim(value=200, tag="invalid", short="", long="")
60+
) # Invalid value (>127)
61+
62+
63+
def test_validate_trust_vector(valid_trust_vector):
64+
# Should not raise an error
65+
validate_trust_vector(valid_trust_vector)
66+
67+
68+
def test_validate_trust_vector_invalid():
69+
with pytest.raises(EARValidationError):
70+
invalid_vector = TrustVector(
71+
configuration=TrustClaim(value=200, tag="invalid", short="", long="")
72+
)
73+
validate_trust_vector(invalid_vector)
74+
75+
76+
def test_validate_verifier_id(valid_verifier_id):
77+
# Should not raise an error
78+
validate_verifier_id(valid_verifier_id)
79+
80+
81+
def test_validate_verifier_id_invalid():
82+
with pytest.raises(EARValidationError):
83+
validate_verifier_id(VerifierID(developer="", build="")) # Invalid empty fields
84+
85+
86+
def test_validate_ear_claims(valid_ear_claims):
87+
# Should not raise an error
88+
validate_ear_claims(valid_ear_claims)
89+
90+
91+
def test_validate_ear_claims_invalid():
92+
with pytest.raises(EARValidationError):
93+
invalid_claims = EARClaims(
94+
profile="", issued_at=-1, verifier_id=VerifierID(developer="", build="")
95+
)
96+
validate_ear_claims(invalid_claims)

0 commit comments

Comments
 (0)