SHA256
Первая версия вложенных файлов работает
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# HTTP API зашифрованных файлов личных сообщений
|
||||
|
||||
## 1. Назначение
|
||||
|
||||
Файлы личных сообщений хранятся только на access-сервере отправителя и только в зашифрованном виде.
|
||||
HTTP API не получает исходное имя файла, MIME-тип или ключ расшифрования.
|
||||
|
||||
Клиент:
|
||||
|
||||
1. генерирует случайный AES-256-GCM key и 96-bit IV;
|
||||
2. шифрует файл в браузере;
|
||||
3. считает `SHA-256(ciphertext)`;
|
||||
4. переводит 32-byte hash в Base58 — это `fileId` и имя файла на диске сервера;
|
||||
5. загружает ciphertext на свой access-сервер;
|
||||
6. помещает URL, key, IV, исходное имя/MIME/размер в `<S:file...>` внутри plaintext DM;
|
||||
7. обычный механизм E2EE DM шифрует эту техническую вставку отдельно для отправителя и получателя.
|
||||
|
||||
Следствие: сервер файлов не знает AES-key и не может расшифровать вложение.
|
||||
|
||||
## 2. Конфигурация сервера
|
||||
|
||||
```properties
|
||||
dm.files.enabled=true
|
||||
dm.files.storageDir=data/dm-files
|
||||
dm.files.maxBytes=52428816
|
||||
```
|
||||
|
||||
`dm.files.maxBytes` ограничивает размер ciphertext. Текущий официальный UI ограничивает исходный файл 50 MiB; AES-GCM добавляет 16-byte authentication tag.
|
||||
|
||||
## 3. PUT `/dm-files/{fileId}`
|
||||
|
||||
Загружает immutable ciphertext.
|
||||
|
||||
`fileId` обязан быть равен:
|
||||
|
||||
```text
|
||||
Base58(SHA-256(ciphertext))
|
||||
```
|
||||
|
||||
### Заголовки авторизации
|
||||
|
||||
```text
|
||||
Content-Type: application/octet-stream
|
||||
X-Shine-Session-Id: <active session id>
|
||||
X-Shine-Time-Ms: <unix time ms>
|
||||
X-Shine-Content-Length: <ciphertext bytes>
|
||||
X-Shine-Signature: <Ed25519 signature, Base64>
|
||||
```
|
||||
|
||||
Подписываемая UTF-8 строка:
|
||||
|
||||
```text
|
||||
DM_FILE_UPLOAD_V1:{sessionId}:{fileId}:{encryptedSize}:{timeMs}
|
||||
```
|
||||
|
||||
Подпись проверяется публичным `session_key` из активной сессии. Допустимое отклонение времени — 60 секунд.
|
||||
|
||||
Сервер потоково пишет временный файл, одновременно считает SHA-256, проверяет фактический размер и только после успешной проверки атомарно перемещает объект под именем `{fileId}`.
|
||||
|
||||
Повторная корректно подписанная загрузка уже существующего immutable объекта идемпотентна.
|
||||
|
||||
Успешный ответ:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"fileId": "...",
|
||||
"size": 12345,
|
||||
"alreadyExists": false
|
||||
}
|
||||
```
|
||||
|
||||
Основные ошибки: `BAD_FILE_ID`, `MISSING_UPLOAD_AUTH`, `UPLOAD_AUTH_EXPIRED`, `BAD_UPLOAD_SIGNATURE`, `FILE_TOO_LARGE`, `LENGTH_MISMATCH`, `HASH_MISMATCH`.
|
||||
|
||||
## 4. GET `/dm-files/{fileId}`
|
||||
|
||||
Возвращает только сохранённый ciphertext как `application/octet-stream`.
|
||||
|
||||
GET не требует пользовательской сессии: `fileId` является непредсказуемым 256-bit content address, а без AES-key содержимое остаётся зашифрованным. AES-key передаётся только внутри E2EE DM.
|
||||
|
||||
Ответ разрешает cross-origin чтение (`Access-Control-Allow-Origin: *`), потому что получатель может быть подключён к другому access-серверу и должен скачать ciphertext непосредственно с сервера отправителя.
|
||||
|
||||
Перед расшифрованием официальный UI повторно проверяет `Base58(SHA-256(downloadedCiphertext)) == fileId`.
|
||||
|
||||
## 5. HEAD и OPTIONS
|
||||
|
||||
- `HEAD /dm-files/{fileId}` возвращает метаданные ciphertext без тела;
|
||||
- `OPTIONS /dm-files/*` обслуживает CORS preflight для браузерного PUT.
|
||||
|
||||
## 6. Reverse proxy
|
||||
|
||||
Caddy/Nginx должен проксировать `/dm-files/*` в тот же Jetty, что обслуживает `/ws`. Route должен находиться до SPA fallback.
|
||||
|
||||
## 7. Ограничения v1
|
||||
|
||||
- файл перед загрузкой целиком читается в память браузера, поэтому UI ограничен 50 MiB;
|
||||
- удаления/TTL/garbage collection пока нет;
|
||||
- если ciphertext уже загружен, а отправка E2EE DM затем не удалась, объект может остаться orphan-файлом;
|
||||
- resumable/chunked upload не входит в v1.
|
||||
Reference in New Issue
Block a user