attested_request/profile.rs
1//! The fixed parts of the TFH RFC 9421 integrity request signing profile.
2//!
3//! The profile pins the signature label, the covered components, the signature parameters and one
4//! `alg` per platform. Nothing here is negotiable by a client.
5
6use std::fmt;
7
8/// Header carrying the Attestation Gateway JWT whose `cnf.jwk` is the signing key.
9pub const INTEGRITY_TOKEN_HEADER: &str = "integrity-token";
10/// RFC 9421 header declaring the covered components and signature parameters.
11pub const SIGNATURE_INPUT_HEADER: &str = "signature-input";
12/// RFC 9421 header carrying the signature bytes.
13pub const SIGNATURE_HEADER: &str = "signature";
14
15/// The only accepted RFC 9421 signature label.
16pub const SIGNATURE_LABEL: &str = "integrity";
17
18/// A covered component of the signature base.
19///
20/// These are the only components the profile resolves, and a request must cover all of them.
21/// Resolving nothing else keeps unsigned headers from ever reaching the signature base.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum Component {
24 /// `@method`: the request method.
25 Method,
26 /// `@scheme`: the scheme the client dialled, taken from server configuration.
27 Scheme,
28 /// `@authority`: the host the client dialled, taken from server configuration.
29 Authority,
30 /// `@path`: the percent-encoded request path, `/` when empty.
31 Path,
32 /// `@query`: the raw query prefixed with `?`, or `?` alone when absent.
33 Query,
34 /// `content-digest`: RFC 9530 SHA-256 digest of the raw body bytes.
35 ContentDigest,
36 /// `integrity-token`: the exact `Integrity-Token` header value.
37 IntegrityToken,
38}
39
40impl Component {
41 /// Every component, in the order signers emit them.
42 pub const ALL: [Self; 7] = [
43 Self::Method,
44 Self::Scheme,
45 Self::Authority,
46 Self::Path,
47 Self::Query,
48 Self::ContentDigest,
49 Self::IntegrityToken,
50 ];
51
52 /// The RFC 9421 component identifier, as it appears in `Signature-Input`.
53 #[must_use]
54 pub const fn identifier(self) -> &'static str {
55 match self {
56 Self::Method => "@method",
57 Self::Scheme => "@scheme",
58 Self::Authority => "@authority",
59 Self::Path => "@path",
60 Self::Query => "@query",
61 Self::ContentDigest => "content-digest",
62 Self::IntegrityToken => "integrity-token",
63 }
64 }
65
66 /// Resolves an identifier. Matching is exact: RFC 9421 field names are lowercase.
67 #[must_use]
68 pub fn from_identifier(identifier: &str) -> Option<Self> {
69 Self::ALL
70 .into_iter()
71 .find(|component| component.identifier() == identifier)
72 }
73}
74
75impl fmt::Display for Component {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 f.write_str(self.identifier())
78 }
79}
80
81/// The attested platform, from the Attestation Gateway token's `platform` claim.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
83pub enum Platform {
84 /// iOS: App Attest keys, signatures are CBOR assertions.
85 Ios,
86 /// Android: hardware-backed keys, signatures are DER-encoded ECDSA.
87 Android,
88}
89
90impl Platform {
91 /// The `alg` signature parameter a request from this platform must carry.
92 #[must_use]
93 pub const fn alg(self) -> &'static str {
94 match self {
95 Self::Ios => "world-integrity-ios-v1",
96 Self::Android => "world-integrity-android-v1",
97 }
98 }
99
100 /// The `platform` claim value the Attestation Gateway issues.
101 #[must_use]
102 pub const fn as_str(self) -> &'static str {
103 match self {
104 Self::Ios => "ios",
105 Self::Android => "android",
106 }
107 }
108
109 /// Parses a `platform` claim value.
110 #[must_use]
111 pub fn from_claim(claim: &str) -> Option<Self> {
112 match claim {
113 "ios" => Some(Self::Ios),
114 "android" => Some(Self::Android),
115 _ => None,
116 }
117 }
118}
119
120impl fmt::Display for Platform {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 f.write_str(self.as_str())
123 }
124}