Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
123 changes: 116 additions & 7 deletions integration-tests/utils/checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,29 @@ def save_result(response, scheme, evidence):
jwt_outfile = f'{GENDIR}/results/{scheme}.{evidence}.jwt'

try:
result = response.json()["result"]
except KeyError:
raise ValueError("Did not receive an attestation result.")
# Handle different response formats
if hasattr(response, 'json'):
response_json = response.json()
elif isinstance(response, dict):
response_json = response
else:
response_json = response

# Try different key names for the result
result = None
if isinstance(response_json, dict):
if "result" in response_json:
result = response_json["result"]
elif "attestation_result" in response_json:
result = response_json["attestation_result"]
elif "jwt" in response_json:
result = response_json["jwt"]

if result is None:
raise ValueError("Did not receive an attestation result.")

except (KeyError, AttributeError, TypeError) as e:
raise ValueError(f"Did not receive an attestation result: {e}")

with open(jwt_outfile, 'w') as wfh:
wfh.write(result)
Expand All @@ -27,7 +47,41 @@ def save_result(response, scheme, evidence):


def compare_to_expected_result(response, expected, verifier_key):
decoded_submods = _extract_submods(response, verifier_key)
# Handle Box objects (which Tavern uses internally)
if hasattr(response, 'to_dict'):
response_data = response.to_dict()
elif hasattr(response, '__dict__'):
response_data = response.__dict__
else:
response_data = response

# Try to extract submods using different approaches
decoded_submods = None

# First try: Use the original method if response_data has a 'json' method
if hasattr(response_data, 'json'):
try:
decoded_submods = _extract_submods(response_data, verifier_key)
except (AttributeError, TypeError, ValueError, KeyError):
# Fall back to dictionary method
try:
if hasattr(response_data, 'json'):
json_data = response_data.json()
decoded_submods = _extract_submods_from_dict(json_data, verifier_key)
except (AttributeError, TypeError, ValueError, KeyError):
pass

# Second try: Extract directly from dictionary/response data
if decoded_submods is None:
try:
decoded_submods = _extract_submods_from_dict(response_data, verifier_key)
except (AttributeError, TypeError, ValueError, KeyError):
# If we still can't extract, check if it's already the expected format
if isinstance(response_data, dict) and any(key.startswith('urn:') for key in response_data.keys()):
# It might already be the submods data
decoded_submods = response_data
else:
raise ValueError("Could not extract attestation result from response")

with open(expected) as fh:
expected_submods = json.load(fh)
Expand All @@ -38,6 +92,7 @@ def compare_to_expected_result(response, expected, verifier_key):
print("Key exists in the dictionary.")
except KeyError:
print(f"Key {key} does not exist in the dictionary.")
raise

assert decoded_claims["ear.status"] == expected_claims["ear.status"]
print(f"Evaluating Submod with SubModName {key}")
Expand Down Expand Up @@ -96,9 +151,29 @@ def _check_within_period(dt, period):

def _extract_submods(response, key_file):
try:
result = response.json()["result"]
except KeyError:
raise ValueError("Did not receive an attestation result.")
# Handle different response formats
if hasattr(response, 'json'):
response_json = response.json()
elif isinstance(response, dict):
response_json = response
else:
response_json = response

# Try different key names for the result
result = None
if isinstance(response_json, dict):
if "result" in response_json:
result = response_json["result"]
elif "attestation_result" in response_json:
result = response_json["attestation_result"]
elif "jwt" in response_json:
result = response_json["jwt"]

if result is None:
raise ValueError("Did not receive an attestation result.")

except (KeyError, AttributeError, TypeError) as e:
raise ValueError(f"Did not receive an attestation result: {e}")

with open(key_file) as fh:
key = json.load(fh)
Expand All @@ -108,6 +183,40 @@ def _extract_submods(response, key_file):
return decoded["submods"]


def _extract_submods_from_dict(response_data, key_file):
"""Extract submods from a dictionary/Box object instead of a response object"""
result = None

# Try different ways to extract the result
if isinstance(response_data, dict):
# Try the standard "result" key
if "result" in response_data:
result = response_data["result"]
# Try alternative key names that might be used
elif "attestation_result" in response_data:
result = response_data["attestation_result"]
elif "jwt" in response_data:
result = response_data["jwt"]
# Check if the response_data itself might be the JWT token
elif isinstance(response_data.get('body'), str) and response_data['body'].count('.') == 2:
result = response_data['body']
elif isinstance(response_data, str) and response_data.count('.') == 2:
# It might be a JWT token itself
result = response_data

if result is None:
raise ValueError("Did not receive an attestation result.")

with open(key_file) as fh:
key = json.load(fh)

try:
decoded = jwt.decode(result, key=key, algorithms=['ES256'])
return decoded["submods"]
except Exception as e:
raise ValueError(f"Failed to decode JWT token: {e}")


def _extract_policy(data):
policy = data
policy['ctime'] = datetime.fromisoformat(policy['ctime'])
Expand Down
4 changes: 1 addition & 3 deletions integration-tests/utils/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,9 @@ def generate_evidence(scheme, evidence, nonce, signing, outname):
)
elif scheme == 'cca' and nonce:
claims_file = f'{GENDIR}/claims/{scheme}.{evidence}.json'
# convert nonce from base64url to base64
translated_nonce = nonce.replace('-', '+').replace('_', '/')
update_json(
f'data/claims/{scheme}.{evidence}.json',
{'cca-realm-delegated-token': {f'cca-realm-challenge': translated_nonce}},
{'cca-realm-delegated-token': {f'cca-realm-challenge': nonce}},
claims_file,
)
else:
Expand Down
8 changes: 4 additions & 4 deletions scheme/arm-cca/evidence_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package arm_cca

import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"

Expand Down Expand Up @@ -75,6 +74,8 @@ func (s EvidenceHandler) ValidateEvidenceIntegrity(
return handler.BadEvidence(err)
}

// Expect the challenge in the CCA token to be base64url as the server's nonce is base64url.
// The challenge is extracted from the challenge-response session maintained by the server.
realmChallenge, err := ccaToken.RealmClaims.GetChallenge()
if err != nil {
return handler.BadEvidence(err)
Expand All @@ -88,9 +89,8 @@ func (s EvidenceHandler) ValidateEvidenceIntegrity(

if !bytes.Equal(realmChallenge, sessionNonce) {
return handler.BadEvidence(
"freshness: realm challenge (%s) does not match session nonce (%s)",
hex.EncodeToString(realmChallenge),
hex.EncodeToString(token.Nonce),
"freshness: realm challenge (%x) does not match session nonce (%x)",
realmChallenge, token.Nonce,
)
}

Expand Down
11 changes: 5 additions & 6 deletions scheme/psa-iot/evidence_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package psa_iot

import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"log"
Expand Down Expand Up @@ -63,12 +62,12 @@ func (s EvidenceHandler) ValidateEvidenceIntegrity(
if err != nil {
return handler.BadEvidence(err)
}

// Expect the nonce in the PSA token to be base64url as the server's nonce is base64url
if !bytes.Equal(psaNonce, token.Nonce) {
return handler.BadEvidence(
"freshness: psa-nonce (%s) does not match session nonce (%s)",
hex.EncodeToString(psaNonce),
hex.EncodeToString(token.Nonce),
)
return handler.BadEvidence("freshness: psa-nonce (%x) does not match session nonce (%x)",
psaNonce, token.Nonce,
)
}

pk, err := arm.GetPublicKeyFromTA(SchemeName, trustAnchors[0])
Expand Down
29 changes: 28 additions & 1 deletion verification/api/challengeresponsesession.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package api

import (
"encoding/base64"
"encoding/json"
"fmt"
"time"
Expand Down Expand Up @@ -64,6 +65,32 @@ func (o *Status) UnmarshalJSON(b []byte) error {
return o.FromString(s)
}

// URLSafeNonce is a wrapper around []byte that marshals/unmarshals using URL-safe base64
type URLSafeNonce []byte

func (n URLSafeNonce) MarshalJSON() ([]byte, error) {
if n == nil {
return []byte("null"), nil
}
encoded := base64.URLEncoding.EncodeToString(n)
return json.Marshal(encoded)
}

func (n *URLSafeNonce) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}

decoded, err := base64.URLEncoding.DecodeString(s)
if err != nil {
return err
}

*n = URLSafeNonce(decoded)
return nil
}

type EvidenceBlob struct {
Type string `json:"type"`
Value []byte `json:"value"`
Expand All @@ -72,7 +99,7 @@ type EvidenceBlob struct {
type ChallengeResponseSession struct {
id string
Status Status `json:"status"`
Nonce []byte `json:"nonce"`
Nonce URLSafeNonce `json:"nonce"`
Expiry time.Time `json:"expiry"`
Accept []string `json:"accept"`
Evidence *EvidenceBlob `json:"evidence,omitempty"`
Expand Down
4 changes: 2 additions & 2 deletions verification/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func newSession(nonce []byte, supportedMediaTypes []string, ttl time.Duration) (
session := &ChallengeResponseSession{
id: id.String(),
Status: StatusWaiting, // start in waiting status
Nonce: nonce,
Nonce: URLSafeNonce(nonce),
Expiry: time.Now().Add(ttl), // RFC3339 format, with sub-second precision added if present
Accept: supportedMediaTypes,
}
Expand Down Expand Up @@ -394,7 +394,7 @@ func (o *Handler) SubmitEvidence(c *gin.Context) {
// reported if something in the verifier or the connection goes wrong.
// Any problems with the evidence are expected to be reported via the
// attestation result.
attestationResult, err := o.Verifier.ProcessEvidence(tenantID, session.Nonce,
attestationResult, err := o.Verifier.ProcessEvidence(tenantID, []byte(session.Nonce),
evidence, mediaType)
if err != nil {
o.logger.Error(err)
Expand Down
Loading