fabrica

hanna/fabrica

7776 bytes
Raw
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Public keys: SSH (push auth + signatures) and GPG (signatures).
6
7use model::{Key, KeyKind};
8use sqlx::any::AnyRow;
9use sqlx::{AssertSqlSafe, Row};
10
11use crate::{Store, StoreError, map_conflict, new_id, now_ms};
12
13/// The columns of `keys` in a fixed order, shared by every `SELECT`.
14const KEY_COLUMNS: &str = "id, user_id, kind, name, fingerprint, public_key, ordinal, \
15 last_used_at, verified_at, created_at";
16
17/// Fields needed to register a key. The fingerprint is computed by the caller
18/// (which parses and validates the key material); the store assigns the id and
19/// ordinal.
20#[derive(Debug, Clone)]
21pub struct NewKey {
22 /// Owning user id.
23 pub user_id: String,
24 /// SSH or GPG.
25 pub kind: KeyKind,
26 /// Optional label.
27 pub name: Option<String>,
28 /// `SHA256:…` (SSH) or hex (GPG) fingerprint.
29 pub fingerprint: String,
30 /// The `authorized_keys` line or armored block.
31 pub public_key: String,
32}
33
34impl Store {
35 /// Register a key, assigning the next ordinal for its owner.
36 ///
37 /// Ordinals are assigned unique **per user** (across both kinds), not merely
38 /// per `(user, kind)`, so `fabrica key del <user> <index>` needs no `--type` to
39 /// be unambiguous. Deleting a key never renumbers the survivors.
40 ///
41 /// # Errors
42 ///
43 /// * [`StoreError::Conflict`] if the fingerprint is already registered.
44 /// * [`StoreError::Query`] on any other database failure.
45 pub async fn create_key(&self, new: NewKey) -> Result<Key, StoreError> {
46 let next: i64 = sqlx::query(
47 "SELECT COALESCE(MAX(ordinal), 0) + 1 AS next FROM keys WHERE user_id = $1",
48 )
49 .bind(&new.user_id)
50 .fetch_one(&self.pool)
51 .await?
52 .try_get("next")?;
53
54 let key = Key {
55 id: new_id(),
56 user_id: new.user_id,
57 kind: new.kind,
58 name: new.name,
59 fingerprint: new.fingerprint,
60 public_key: new.public_key,
61 ordinal: next,
62 last_used_at: None,
63 verified_at: None,
64 created_at: now_ms(),
65 };
66
67 let sql = "INSERT INTO keys \
68 (id, user_id, kind, name, fingerprint, public_key, ordinal, last_used_at, \
69 verified_at, created_at) \
70 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
71 sqlx::query(sql)
72 .bind(key.id.clone())
73 .bind(key.user_id.clone())
74 .bind(key.kind.as_str())
75 .bind(key.name.clone())
76 .bind(key.fingerprint.clone())
77 .bind(key.public_key.clone())
78 .bind(key.ordinal)
79 .bind(key.last_used_at)
80 .bind(key.verified_at)
81 .bind(key.created_at)
82 .execute(&self.pool)
83 .await
84 .map_err(|e| map_conflict(e, "key", &[("fingerprint", "fingerprint")]))?;
85
86 Ok(key)
87 }
88
89 /// List a user's keys, ordered by ordinal (i.e. registration order).
90 ///
91 /// # Errors
92 ///
93 /// Returns [`StoreError::Query`] on a database failure.
94 pub async fn keys_by_user(&self, user_id: &str) -> Result<Vec<Key>, StoreError> {
95 let sql = format!("SELECT {KEY_COLUMNS} FROM keys WHERE user_id = $1 ORDER BY ordinal ASC");
96 let rows = sqlx::query(AssertSqlSafe(sql))
97 .bind(user_id)
98 .fetch_all(&self.pool)
99 .await?;
100 rows.iter()
101 .map(map_key)
102 .collect::<Result<_, _>>()
103 .map_err(Into::into)
104 }
105
106 /// Every registered key on the instance, for signature verification (the
107 /// signer may be any registered user).
108 ///
109 /// # Errors
110 ///
111 /// Returns [`StoreError::Query`] on a database failure.
112 pub async fn all_keys(&self) -> Result<Vec<Key>, StoreError> {
113 let sql = format!("SELECT {KEY_COLUMNS} FROM keys");
114 let rows = sqlx::query(AssertSqlSafe(sql))
115 .fetch_all(&self.pool)
116 .await?;
117 rows.iter()
118 .map(map_key)
119 .collect::<Result<_, _>>()
120 .map_err(Into::into)
121 }
122
123 /// Fetch a user's key by its 1-based ordinal, or `None`.
124 ///
125 /// # Errors
126 ///
127 /// Returns [`StoreError::Query`] on a database failure.
128 pub async fn key_by_ordinal(
129 &self,
130 user_id: &str,
131 ordinal: i64,
132 ) -> Result<Option<Key>, StoreError> {
133 let sql = format!("SELECT {KEY_COLUMNS} FROM keys WHERE user_id = $1 AND ordinal = $2");
134 let row = sqlx::query(AssertSqlSafe(sql))
135 .bind(user_id)
136 .bind(ordinal)
137 .fetch_optional(&self.pool)
138 .await?;
139 Ok(row.as_ref().map(map_key).transpose()?)
140 }
141
142 /// Look up any key by `(kind, fingerprint)` — used to reject a duplicate before
143 /// insert and name the existing owner, and by SSH auth to bind a session.
144 ///
145 /// # Errors
146 ///
147 /// Returns [`StoreError::Query`] on a database failure.
148 pub async fn key_by_fingerprint(
149 &self,
150 kind: KeyKind,
151 fingerprint: &str,
152 ) -> Result<Option<Key>, StoreError> {
153 let sql = format!("SELECT {KEY_COLUMNS} FROM keys WHERE kind = $1 AND fingerprint = $2");
154 let row = sqlx::query(AssertSqlSafe(sql))
155 .bind(kind.as_str())
156 .bind(fingerprint)
157 .fetch_optional(&self.pool)
158 .await?;
159 Ok(row.as_ref().map(map_key).transpose()?)
160 }
161
162 /// Stamp a key's `last_used_at` with the current time (SSH auth binding).
163 ///
164 /// # Errors
165 ///
166 /// Returns [`StoreError::Query`] on a database failure.
167 pub async fn touch_key_used(&self, key_id: &str) -> Result<(), StoreError> {
168 sqlx::query("UPDATE keys SET last_used_at = $1 WHERE id = $2")
169 .bind(now_ms())
170 .bind(key_id)
171 .execute(&self.pool)
172 .await?;
173 Ok(())
174 }
175
176 /// Mark a key's ownership verified (idempotent; sets `verified_at` once).
177 ///
178 /// # Errors
179 ///
180 /// Returns [`StoreError::Query`] on a database failure.
181 pub async fn set_key_verified(&self, key_id: &str) -> Result<(), StoreError> {
182 sqlx::query("UPDATE keys SET verified_at = $1 WHERE id = $2 AND verified_at IS NULL")
183 .bind(now_ms())
184 .bind(key_id)
185 .execute(&self.pool)
186 .await?;
187 Ok(())
188 }
189
190 /// Delete a key by id. Returns `true` if a row was removed.
191 ///
192 /// # Errors
193 ///
194 /// Returns [`StoreError::Query`] on a database failure.
195 pub async fn delete_key(&self, key_id: &str) -> Result<bool, StoreError> {
196 let deleted = sqlx::query("DELETE FROM keys WHERE id = $1")
197 .bind(key_id)
198 .execute(&self.pool)
199 .await?
200 .rows_affected();
201 Ok(deleted > 0)
202 }
203}
204
205/// Map a `keys` row (selected via [`KEY_COLUMNS`]) to a [`Key`].
206fn map_key(row: &AnyRow) -> Result<Key, sqlx::Error> {
207 let kind_str: String = row.try_get("kind")?;
208 let kind = KeyKind::from_token(&kind_str)
209 .ok_or_else(|| sqlx::Error::Decode(format!("invalid key kind {kind_str:?}").into()))?;
210 Ok(Key {
211 id: row.try_get("id")?,
212 user_id: row.try_get("user_id")?,
213 kind,
214 name: row.try_get("name")?,
215 fingerprint: row.try_get("fingerprint")?,
216 public_key: row.try_get("public_key")?,
217 ordinal: row.try_get("ordinal")?,
218 last_used_at: row.try_get("last_used_at")?,
219 verified_at: row.try_get("verified_at")?,
220 created_at: row.try_get("created_at")?,
221 })
222}