diff --git a/Cargo.lock b/Cargo.lock
index 526bb4d..087252b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3293,9 +3293,9 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.34"
+version = "0.3.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
+checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa",
@@ -3314,9 +3314,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
-version = "0.2.17"
+version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
+checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
dependencies = [
"num-conv",
"time-core",
diff --git a/src/attestation_store/cf_kv.rs b/src/attestation_store/cf_kv.rs
new file mode 100644
index 0000000..3a04c28
--- /dev/null
+++ b/src/attestation_store/cf_kv.rs
@@ -0,0 +1,88 @@
+use {
+ super::{AttestationStore, Result},
+ crate::http_server::{CsrfToken, TokenManager},
+ async_trait::async_trait,
+ hyper::StatusCode,
+ reqwest::Url,
+ serde::Serialize,
+ std::time::Duration,
+};
+
+#[derive(Clone)]
+pub struct CloudflareKv {
+ pub endpoint: Url,
+ pub token_manager: TokenManager,
+ pub http_client: reqwest::Client,
+}
+
+impl CloudflareKv {
+ pub fn new(endpoint: Url, token_manager: TokenManager) -> Self {
+ Self {
+ endpoint,
+ token_manager,
+ http_client: reqwest::Client::new(),
+ }
+ }
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+struct SetAttestationCompatBody<'a> {
+ attestation_id: &'a str,
+ origin: &'a str,
+}
+
+#[async_trait]
+impl AttestationStore for CloudflareKv {
+ async fn set_attestation(&self, id: &str, origin: &str) -> Result<()> {
+ let url = self.endpoint.join("/attestation")?;
+ let res = self
+ .http_client
+ .post(url)
+ .header(
+ CsrfToken::header_name(),
+ self.token_manager
+ .generate_csrf_token()
+ .map_err(|e| anyhow::anyhow!("{e:?}"))?,
+ )
+ .json(&SetAttestationCompatBody {
+ attestation_id: id,
+ origin,
+ })
+ .timeout(Duration::from_secs(1))
+ .send()
+ .await?;
+ if res.status().is_success() {
+ Ok(())
+ } else {
+ Err(anyhow::anyhow!(
+ "Failed to set attestation: status:{} response body:{:?}",
+ res.status(),
+ res.text().await
+ ))
+ }
+ }
+
+ async fn get_attestation(&self, id: &str) -> Result