Skip to main content

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}