1use async_trait::async_trait;
2use axum::body::Body;
3use axum::extract::{Request, State};
4use axum::http::header::{ALLOW, CACHE_CONTROL, CONTENT_TYPE, HOST};
5use axum::http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri};
6use axum::middleware::{self, Next};
7use axum::response::Response;
8use axum::routing::{MethodRouter, get};
9use axum::{Json, Router};
10use kcode_k1_http_replay::{ReplayError, ReplayWindow};
11use kcode_k1_http_request::{Envelope, VerifyError, registration_public_key};
12use serde::Serialize;
13use std::fmt::{self, Display, Formatter};
14use std::sync::Arc;
15use tower_http::cors::{Any, CorsLayer};
16
17pub use kcode_k1_http_request::CanonicalUsername;
18
19pub struct Config {
20 pub server_id: String,
21 pub public_origin: String,
22 pub max_body_bytes: usize,
23}
24
25#[derive(Clone, Copy, Eq, PartialEq)]
26pub struct Identity {
27 user_id: [u8; 12],
28 public_key: [u8; 32],
29}
30
31impl Identity {
32 pub fn new(user_id: [u8; 12], public_key: [u8; 32]) -> Self {
33 Self {
34 user_id,
35 public_key,
36 }
37 }
38
39 pub fn user_id(&self) -> &[u8; 12] {
40 &self.user_id
41 }
42
43 pub fn public_key(&self) -> &[u8; 32] {
44 &self.public_key
45 }
46}
47
48#[derive(Clone)]
49pub struct Principal {
50 user_id: [u8; 12],
51 username: CanonicalUsername,
52}
53
54impl Principal {
55 pub fn user_id(&self) -> &[u8; 12] {
56 &self.user_id
57 }
58
59 pub fn username(&self) -> &CanonicalUsername {
60 &self.username
61 }
62}
63
64#[derive(Clone)]
65pub struct RegistrationPrincipal {
66 username: CanonicalUsername,
67 public_key: [u8; 32],
68}
69
70impl RegistrationPrincipal {
71 pub fn username(&self) -> &CanonicalUsername {
72 &self.username
73 }
74
75 pub fn public_key(&self) -> &[u8; 32] {
76 &self.public_key
77 }
78}
79
80#[derive(Clone, Copy, Debug, Eq, PartialEq)]
81pub enum IdentityError {
82 Unavailable,
83}
84
85impl Display for IdentityError {
86 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
87 formatter.write_str("identity provider unavailable")
88 }
89}
90
91impl std::error::Error for IdentityError {}
92
93#[async_trait]
94pub trait IdentityProvider: Send + Sync + 'static {
95 async fn lookup(&self, username: &CanonicalUsername)
96 -> Result<Option<Identity>, IdentityError>;
97}
98
99#[derive(Clone, Copy, Debug, Eq, PartialEq)]
100pub enum ConfigError {
101 EmptyServerId,
102 InvalidPublicOrigin,
103 InvalidBodyLimit,
104}
105
106impl Display for ConfigError {
107 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
108 formatter.write_str(match self {
109 Self::EmptyServerId => "server ID is empty",
110 Self::InvalidPublicOrigin => "public origin is invalid",
111 Self::InvalidBodyLimit => "body limit is invalid",
112 })
113 }
114}
115
116impl std::error::Error for ConfigError {}
117
118pub struct K1Http {
119 state: Arc<AppState>,
120}
121
122struct AppState {
123 server_id: String,
124 public_origin: String,
125 authority: String,
126 max_body_bytes: usize,
127 replay: ReplayWindow,
128 identities: Arc<dyn IdentityProvider>,
129}
130
131#[derive(Serialize)]
132struct ConfigResponse {
133 protocol: &'static str,
134 server_id: String,
135 public_origin: String,
136}
137
138impl K1Http {
139 pub fn new(
140 config: Config,
141 replay: kcode_k1_http_replay::ReplayWindow,
142 identities: Arc<dyn IdentityProvider>,
143 ) -> Result<Self, ConfigError> {
144 if config.server_id.is_empty() {
145 return Err(ConfigError::EmptyServerId);
146 }
147 if config.max_body_bytes == 0 {
148 return Err(ConfigError::InvalidBodyLimit);
149 }
150 let authority =
151 origin_authority(&config.public_origin).ok_or(ConfigError::InvalidPublicOrigin)?;
152 Ok(Self {
153 state: Arc::new(AppState {
154 server_id: config.server_id,
155 public_origin: config.public_origin,
156 authority,
157 max_body_bytes: config.max_body_bytes,
158 replay,
159 identities,
160 }),
161 })
162 }
163
164 pub fn router(
165 &self,
166 registration: MethodRouter,
167 terms: MethodRouter,
168 authenticated: Router,
169 ) -> Router {
170 let authenticated = authenticated.layer(middleware::from_fn_with_state(
171 self.state.clone(),
172 authenticate,
173 ));
174 let registration = registration.layer(middleware::from_fn_with_state(
175 self.state.clone(),
176 authenticate_registration,
177 ));
178 let configuration = get(configuration).with_state(self.state.clone());
179 Router::new()
180 .nest("/api", authenticated)
181 .route("/api/register", registration)
182 .route("/api/terms", terms)
183 .route("/api/config.json", configuration)
184 .layer(middleware::from_fn(terms_method_guard))
185 .layer(cors())
186 .layer(middleware::from_fn_with_state(self.state.clone(), api_gate))
187 }
188}
189
190async fn configuration(State(state): State<Arc<AppState>>) -> Json<ConfigResponse> {
191 Json(ConfigResponse {
192 protocol: "K1-HTTP-1",
193 server_id: state.server_id.clone(),
194 public_origin: state.public_origin.clone(),
195 })
196}
197
198fn origin_authority(value: &str) -> Option<String> {
199 let uri: Uri = value.parse().ok()?;
200 let scheme = uri.scheme_str()?;
201 if !scheme.eq_ignore_ascii_case("http") && !scheme.eq_ignore_ascii_case("https") {
202 return None;
203 }
204 let authority = uri.authority()?.as_str();
205 if authority.is_empty() || authority.contains('@') || uri.query().is_some() {
206 return None;
207 }
208 if !matches!(uri.path(), "" | "/") {
209 return None;
210 }
211 Some(authority.to_owned())
212}
213
214fn cors() -> CorsLayer {
215 CorsLayer::new()
216 .allow_origin(Any)
217 .allow_methods([
218 Method::GET,
219 Method::POST,
220 Method::PUT,
221 Method::PATCH,
222 Method::DELETE,
223 Method::OPTIONS,
224 Method::HEAD,
225 ])
226 .allow_headers([
227 CONTENT_TYPE,
228 header("k1-username"),
229 header("k1-epoch"),
230 header("k1-nonce"),
231 header("k1-body-sha256"),
232 header("k1-signature"),
233 header("k1-public-key"),
234 ])
235 .expose_headers([header("k1-epoch")])
236}
237
238fn header(value: &'static str) -> HeaderName {
239 HeaderName::from_static(value)
240}
241
242fn set(headers: &mut HeaderMap, name: HeaderName, value: &'static str) {
243 headers.insert(name, HeaderValue::from_static(value));
244}
245
246async fn api_gate(State(state): State<Arc<AppState>>, request: Request, next: Next) -> Response {
247 let authority_ok = single(request.headers(), HOST.as_str())
248 .is_ok_and(|value| value.as_bytes() == state.authority.as_bytes());
249 if !authority_ok {
250 return finish(
251 &state,
252 error(StatusCode::MISDIRECTED_REQUEST, "invalid_request_authority"),
253 )
254 .await;
255 }
256 if state.replay.current_epoch().await.is_err() {
257 return decorate(
258 error(StatusCode::SERVICE_UNAVAILABLE, "epoch_unavailable"),
259 None,
260 );
261 }
262 finish(&state, next.run(request).await).await
263}
264
265async fn terms_method_guard(request: Request, next: Next) -> Response {
266 if request.uri().path() == "/api/terms"
267 && request.method() != Method::GET
268 && request.method() != Method::HEAD
269 {
270 return method_not_allowed("GET, HEAD");
271 }
272 next.run(request).await
273}
274
275async fn finish(state: &AppState, response: Response) -> Response {
276 match state.replay.current_epoch().await {
277 Ok(epoch) => decorate(response, Some(epoch)),
278 Err(_) => decorate(
279 error(StatusCode::SERVICE_UNAVAILABLE, "epoch_unavailable"),
280 None,
281 ),
282 }
283}
284
285fn decorate(mut response: Response, epoch: Option<u64>) -> Response {
286 let headers = response.headers_mut();
287 set(headers, CACHE_CONTROL, "no-store");
288 set(headers, header("x-content-type-options"), "nosniff");
289 set(headers, header("access-control-allow-origin"), "*");
290 set(headers, header("access-control-expose-headers"), "k1-epoch");
291 if let Some(epoch) = epoch
292 && let Ok(value) = HeaderValue::from_str(&epoch.to_string())
293 {
294 headers.insert(header("k1-epoch"), value);
295 }
296 response
297}
298
299fn single<'a>(headers: &'a HeaderMap, name: &str) -> Result<&'a HeaderValue, ()> {
300 let mut values = headers.get_all(name).iter();
301 let value = values.next().ok_or(())?;
302 if values.next().is_some() {
303 return Err(());
304 }
305 Ok(value)
306}
307
308async fn authenticate(
309 State(state): State<Arc<AppState>>,
310 request: Request,
311 next: Next,
312) -> Response {
313 proceed(authenticated_request(&state, request).await, next).await
314}
315
316async fn authenticate_registration(
317 State(state): State<Arc<AppState>>,
318 request: Request,
319 next: Next,
320) -> Response {
321 if request.method() != Method::POST {
322 return method_not_allowed("POST");
323 }
324 proceed(registration_request(&state, request).await, next).await
325}
326
327async fn proceed(result: Result<Request, Response>, next: Next) -> Response {
328 match result {
329 Ok(request) => next.run(request).await,
330 Err(response) => response,
331 }
332}
333
334async fn authenticated_request(state: &AppState, request: Request) -> Result<Request, Response> {
335 let envelope = Envelope::parse(request.headers())
336 .map_err(|_| error(StatusCode::BAD_REQUEST, "malformed_authentication_envelope"))?;
337 validate_epoch(state, envelope.epoch()).await?;
338 let identity = match state.identities.lookup(envelope.username()).await {
339 Ok(Some(identity)) => identity,
340 Ok(None) => return Err(error(StatusCode::UNAUTHORIZED, "authentication_failed")),
341 Err(IdentityError::Unavailable) => {
342 return Err(error(
343 StatusCode::SERVICE_UNAVAILABLE,
344 "identity_provider_unavailable",
345 ));
346 }
347 };
348 let mut request = verified(state, &envelope, request, identity.public_key()).await?;
349 let mut replay_id = [0_u8; 32];
350 replay_id[..12].copy_from_slice(identity.user_id());
351 state
352 .replay
353 .admit(&replay_id, envelope.epoch(), *envelope.nonce())
354 .await
355 .map_err(replay_error)?;
356 request.extensions_mut().insert(Principal {
357 user_id: *identity.user_id(),
358 username: envelope.username().clone(),
359 });
360 Ok(request)
361}
362
363async fn registration_request(state: &AppState, request: Request) -> Result<Request, Response> {
364 let envelope = Envelope::parse(request.headers())
365 .map_err(|_| error(StatusCode::BAD_REQUEST, "malformed_authentication_envelope"))?;
366 if single(request.headers(), "k1-public-key").is_err() {
367 return Err(error(
368 StatusCode::BAD_REQUEST,
369 "malformed_authentication_envelope",
370 ));
371 }
372 let candidate = registration_public_key(request.headers())
373 .map_err(|_| error(StatusCode::UNAUTHORIZED, "authentication_failed"))?;
374 validate_epoch(state, envelope.epoch()).await?;
375 let mut request = verified(state, &envelope, request, &candidate).await?;
376 request.extensions_mut().insert(RegistrationPrincipal {
377 username: envelope.username().clone(),
378 public_key: candidate,
379 });
380 Ok(request)
381}
382
383async fn verified(
384 state: &AppState,
385 envelope: &Envelope,
386 request: Request,
387 public_key: &[u8; 32],
388) -> Result<Request, Response> {
389 envelope
390 .verify(
391 request,
392 &state.server_id,
393 &state.public_origin,
394 public_key,
395 state.max_body_bytes,
396 )
397 .await
398 .map_err(verify_error)
399}
400
401async fn validate_epoch(state: &AppState, epoch: u64) -> Result<(), Response> {
402 state
403 .replay
404 .validate_epoch(epoch)
405 .await
406 .map(|_| ())
407 .map_err(replay_error)
408}
409
410fn replay_error(cause: ReplayError) -> Response {
411 match cause {
412 ReplayError::EpochOutsideWindow { .. } => error(StatusCode::UNAUTHORIZED, "stale_epoch"),
413 ReplayError::Replay { .. } => error(StatusCode::CONFLICT, "replay"),
414 ReplayError::CapacityExceeded { .. } => {
415 error(StatusCode::TOO_MANY_REQUESTS, "nonce_capacity")
416 }
417 _ => error(StatusCode::SERVICE_UNAVAILABLE, "epoch_unavailable"),
418 }
419}
420
421fn verify_error(cause: VerifyError) -> Response {
422 match cause {
423 VerifyError::AuthenticationFailed => {
424 error(StatusCode::UNAUTHORIZED, "authentication_failed")
425 }
426 VerifyError::BodyDigestMismatch => error(StatusCode::BAD_REQUEST, "body_digest_mismatch"),
427 VerifyError::BodyTooLarge => error(StatusCode::PAYLOAD_TOO_LARGE, "body_too_large"),
428 }
429}
430
431fn method_not_allowed(allow: &'static str) -> Response {
432 let mut response = Response::new(Body::empty());
433 *response.status_mut() = StatusCode::METHOD_NOT_ALLOWED;
434 set(response.headers_mut(), ALLOW, allow);
435 response
436}
437
438fn error(status: StatusCode, code: &'static str) -> Response {
439 let mut response = Response::new(Body::from(format!("{{\"error\":\"{code}\"}}")));
440 *response.status_mut() = status;
441 set(response.headers_mut(), CONTENT_TYPE, "application/json");
442 response
443}