| 230 | } |
| 231 | |
| 232 | async fn refresh_known_keys(&mut self) -> Result<()> { |
| 233 | let acme_info_url = format!( |
| 234 | "{}/.dstack/acme-info", |
| 235 | self.gateway_uri.trim_end_matches('/') |
| 236 | ); |
| 237 | info!("fetching known public keys from {}", acme_info_url); |
| 238 | |
| 239 | let response = self |
| 240 | .client |
| 241 | .get(&acme_info_url) |
| 242 | .send() |
| 243 | .await |
| 244 | .context("failed to fetch acme-info")?; |
| 245 | |
| 246 | if !response.status().is_success() { |
| 247 | bail!( |
| 248 | "failed to fetch acme-info: HTTP {}", |
| 249 | response.status().as_u16() |
| 250 | ); |
| 251 | } |
| 252 | |
| 253 | let info: AcmeInfoResponse = response |
| 254 | .json() |
| 255 | .await |
| 256 | .context("failed to parse acme-info response")?; |
| 257 | |
| 258 | info!( |
| 259 | "got {} quoted public keys, verifying...", |
| 260 | info.quoted_hist_keys.len() |
| 261 | ); |
| 262 | |
| 263 | let mut verified_keys = BTreeSet::new(); |
| 264 | for (i, quoted_key) in info.quoted_hist_keys.iter().enumerate() { |
| 265 | match self.verify_quoted_key(quoted_key).await { |
| 266 | Ok((public_key, app_info)) => { |
| 267 | info!( |
| 268 | "✅ verified public key {}: {}", |
| 269 | i, |
| 270 | hex_fmt::HexFmt(&public_key) |
| 271 | ); |
| 272 | info!(" app_id: {}", hex_fmt::HexFmt(&app_info.app_id)); |
| 273 | info!( |
| 274 | " compose_hash: {}", |
| 275 | hex_fmt::HexFmt(&app_info.compose_hash) |
| 276 | ); |
| 277 | info!( |
| 278 | " os_image_hash: {}", |
| 279 | hex_fmt::HexFmt(&app_info.os_image_hash) |
| 280 | ); |
| 281 | verified_keys.insert(public_key); |
| 282 | } |
| 283 | Err(e) => { |
| 284 | warn!( |
| 285 | "⚠️ failed to verify public key {}: {}", |
| 286 | i, |
| 287 | hex_fmt::HexFmt("ed_key.public_key) |
| 288 | ); |
| 289 | warn!(" error: {:#}", e); |