Skip to content
Merged
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
7 changes: 7 additions & 0 deletions docs/concepts/cryptographic-specs.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ Used for:
"capture_id": "550e8400-e29b-41d4-a716-446655440000",
"publisher_id": "9a5b1062-a8fe-4871-bdc1-fe54e96cbf1c",
"device_id": "ea5c9bfe-6bbc-4ee2-b82d-0bcfcc185ef1",
"device_public_key_fingerprint": "4ca63447117ea5c99614bcbe433eb393a1f8b2e14c7b3f5d8e9a0b1c2d3e4f56",
"attestation": {
"method": "app_check",
"app_id": "io.signedshot.capture"
Expand Down Expand Up @@ -314,6 +315,12 @@ def cross_validate(sidecar, jwt_payload):
if sidecar.media_integrity.capture_id != jwt_payload['capture_id']:
return False, "Capture ID mismatch"

# Device public key fingerprint must match (cross-layer binding)
public_key_bytes = base64_decode(sidecar.media_integrity.public_key)
fingerprint = sha256(public_key_bytes).hex()
if fingerprint != jwt_payload['device_public_key_fingerprint']:
return False, "Device public key fingerprint mismatch"

return True, None
```

Expand Down
3 changes: 3 additions & 0 deletions docs/concepts/sidecar-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ The `capture_trust.jwt` is a standard JWT. When decoded, it contains:
"capture_id": "550e8400-e29b-41d4-a716-446655440000",
"publisher_id": "9a5b1062-a8fe-4871-bdc1-fe54e96cbf1c",
"device_id": "ea5c9bfe-6bbc-4ee2-b82d-0bcfcc185ef1",
"device_public_key_fingerprint": "4ca63447117ea5c99614bcbe433eb393...",
"attestation": {
"method": "app_check",
"app_id": "io.signedshot.capture"
Expand All @@ -93,6 +94,7 @@ The `capture_trust.jwt` is a standard JWT. When decoded, it contains:
| `capture_id` | string | Unique capture session ID |
| `publisher_id` | string | Publisher UUID |
| `device_id` | string | Device UUID |
| `device_public_key_fingerprint` | string | SHA-256 of the device's content-signing public key (hex) |
| `attestation` | object | Attestation details |

### attestation Object
Expand Down Expand Up @@ -171,6 +173,7 @@ To verify a sidecar:
- Verify ECDSA signature using `public_key`
4. **Cross-validate**
- Confirm `capture_id` matches in both JWT and media_integrity
- Confirm `SHA-256(public_key)` matches `device_public_key_fingerprint` in the JWT

## Next Steps

Expand Down
7 changes: 7 additions & 0 deletions docs/concepts/two-layer-trust.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Capture Trust answers: **"Was this captured by a legitimate device?"**
| `capture_id` | Unique session identifier |
| `method` | Attestation method: `sandbox`, `app_check`, or `app_attest` |
| `app_id` | App bundle ID (when attested) |
| `device_public_key_fingerprint` | SHA-256 of the device's content-signing public key |
| `issued_at` | Unix timestamp |

### Verification
Expand Down Expand Up @@ -84,6 +85,12 @@ Neither layer alone is sufficient:

Together, they create a complete chain of trust from device verification to content integrity.

### Cross-Layer Binding

The two layers are cryptographically bound through the `device_public_key_fingerprint` — a SHA-256 hash of the device's content-signing public key. This fingerprint is computed at device registration and included in every JWT.

During verification, the validator computes `SHA-256(public_key)` from the media integrity layer and checks it matches the `device_public_key_fingerprint` in the JWT. This prevents an attacker from combining a valid JWT with a media integrity proof signed by a different key.

## The Sidecar File

Both layers are stored in a JSON sidecar file that travels with the media:
Expand Down
9 changes: 9 additions & 0 deletions docs/security/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ SignedShot proves "this device captured this content at this time" — not "this

**Verification:** Check the `method` field in the JWT. Reject `sandbox` for high-trust use cases.

### Cross-Layer Substitution

**Threat:** An attacker performs a legitimate capture to obtain a valid JWT, then generates a new key pair and signs different content with it, combining the valid JWT with the forged media integrity proof.

**Mitigation:** The JWT contains `device_public_key_fingerprint` — the SHA-256 hash of the device's content-signing public key, computed at registration. The validator checks that `SHA-256(public_key from media integrity)` matches this fingerprint, binding the two layers cryptographically.

**Verification:** Confirm `SHA-256(public_key)` matches `device_public_key_fingerprint` in the JWT.

### Metadata Forgery

**Threat:** An attacker manipulates EXIF timestamps, GPS coordinates, or other metadata.
Expand Down Expand Up @@ -182,6 +190,7 @@ For maximum security, verify all of the following:
- [ ] Content hash matches file
- [ ] Media integrity signature valid
- [ ] `capture_id` matches in JWT and media integrity
- [ ] `device_public_key_fingerprint` matches `SHA-256(public_key)` from media integrity
- [ ] `captured_at` is reasonable (not in future, not too old)

## Next Steps
Expand Down