1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
"""
Bundle store — SQLite-backed storage for GEK bundles and keypair bundles.
GEK bundles: ECIES-wrapped GEK targeted at a specific user's X25519 key.
Keypair bundles: AES-GCM encrypted (Ed25519 + X25519) private keys, encrypted
with the user's password-derived bundle_key. Opaque to the node.
Both are stored and served over the P2P DataChannel during MNP handshake.
"""
import logging
from pathlib import Path
import aiosqlite
log = logging.getLogger(__name__)
_SCHEMA_GEK = """\
CREATE TABLE IF NOT EXISTS gek_bundles (
group_id TEXT NOT NULL,
user_id TEXT NOT NULL,
pk_eph_b64 TEXT NOT NULL,
nonce_b64 TEXT NOT NULL,
wrapped_b64 TEXT NOT NULL,
stored_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (group_id, user_id)
);
"""
_SCHEMA_KEYPAIR = """\
CREATE TABLE IF NOT EXISTS keypair_bundles (
user_id TEXT PRIMARY KEY,
bundle_enc TEXT NOT NULL,
stored_at TEXT NOT NULL DEFAULT (datetime('now'))
);
"""
class BundleStore:
def __init__(self, db_path: Path):
self._db_path = db_path
self._db: aiosqlite.Connection | None = None
async def open(self) -> None:
self._db_path.parent.mkdir(parents=True, exist_ok=True)
self._db = await aiosqlite.connect(str(self._db_path))
await self._db.execute(_SCHEMA_GEK)
await self._db.execute(_SCHEMA_KEYPAIR)
await self._db.commit()
async def store(
self,
group_id: str,
user_id: str,
pk_eph_b64: str,
nonce_b64: str,
wrapped_b64: str,
) -> None:
assert self._db
await self._db.execute(
"INSERT OR REPLACE INTO gek_bundles "
"(group_id, user_id, pk_eph_b64, nonce_b64, wrapped_b64, stored_at) "
"VALUES (?, ?, ?, ?, ?, datetime('now'))",
(group_id, user_id, pk_eph_b64, nonce_b64, wrapped_b64),
)
await self._db.commit()
async def fetch(self, group_id: str, user_id: str) -> dict | None:
assert self._db
async with self._db.execute(
"SELECT pk_eph_b64, nonce_b64, wrapped_b64 FROM gek_bundles "
"WHERE group_id = ? AND user_id = ?",
(group_id, user_id),
) as cursor:
row = await cursor.fetchone()
if not row:
return None
return {
"pk_eph_b64": row[0],
"nonce_b64": row[1],
"wrapped_b64": row[2],
}
async def store_keypair(self, user_id: str, bundle_enc: str) -> None:
assert self._db
await self._db.execute(
"INSERT OR REPLACE INTO keypair_bundles "
"(user_id, bundle_enc, stored_at) VALUES (?, ?, datetime('now'))",
(user_id, bundle_enc),
)
await self._db.commit()
async def fetch_keypair(self, user_id: str) -> str | None:
assert self._db
async with self._db.execute(
"SELECT bundle_enc FROM keypair_bundles WHERE user_id = ?",
(user_id,),
) as cursor:
row = await cursor.fetchone()
return row[0] if row else None
async def close(self) -> None:
if self._db:
await self._db.close()
self._db = None
|