An open API service indexing awesome lists of open source software.

https://github.com/btreemap/deadrop


https://github.com/btreemap/deadrop

Last synced: 12 months ago
JSON representation

Awesome Lists containing this project

README

          

# deadrop (WIP Proposal)

**Status:** This document is currently a proposal/Work-in-Progress; no specific timeline for completion has been set.

Encrypted "dead‑drop" service allowing users to anonymously submit and retrieve data using a single X25519 keypair. Data is encrypted client-side and stored server-side under your public key. Retrieval and notification APIs perform a stateless proof-of-possession via a single challenge endpoint returning an encrypted JWT, which is then used with standard `Authorization: Bearer` headers.

---

## Features

* **Client (`deadrop.sh`)**

* `send`: encrypt & upload text or files
* `retrieve`: authenticate & download all ciphertexts for your key
* `notify`: register Telegram push notifications
* Fully stateless challenge using encrypted JWTs; no server-side session storage

* **Server (`deadrop.joefang.org`)**

* `POST /upload`: store encrypted payload
* `POST /challenge`: issue encrypted JWT challenge for a given scope (retrieve/notify)
* `POST /retrieve`: verify JWT & return stored items
* `POST /notify`: verify JWT & register Telegram hook

---

## Prerequisites

* **Client**

* `bash` (≥ 4.4)
* [`age`](https://github.com/FiloSottile/age) (X25519 encryption)
* [`curl`](https://curl.se)
* [`jq`](https://stedolan.github.io/jq)

* **Server**

* [Rust](https://www.rust-lang.org/) (stable toolchain)
* [Docker](https://www.docker.com/)
* Database (e.g. PostgreSQL) for storing ciphertexts and notification registrations
* JWT library supporting HS256 (provided by Rust crate)

---

## Installation

Clone the repo and make the client script executable:

```sh
git clone https://github.com/joefang/deadrop.git
cd deadrop
chmod +x deadrop.sh
```

Server code is in `/server`. See its `Dockerfile` for build instructions and its own README (to be created) for deployment details.

---

## Client Usage (`deadrop.sh`)

### Common flags

* `-k, --pubkey `
X25519 public key (file path or raw string)

* `-i, --identity `
X25519 private key for decryption/authentication

* `-e, --endpoint `
Override default server URL (env var `ENDPOINT` also respected)

### `send`

Encrypt and upload data:

```sh
# upload a string
deadrop.sh send -k id_x25519.pub -m "hello world"

# upload a file
deadrop.sh send -k id_x25519.pub -f /path/to/secret.txt
```

Server endpoint: `POST /upload`
Headers: `X-PubKey: `
Body: binary ciphertext

### `retrieve`

Authenticate via encrypted JWT and download items:

```sh
deadrop.sh retrieve -i id_x25519 -o ./downloads
```

1. Client calls `POST /challenge` with `{ "pubkey": "", "scope": "retrieve" }` → returns `{ "ciphertext": "" }`
2. Client decrypts ciphertext to get the JWT.
3. Client calls `POST /retrieve` with `Authorization: Bearer ` header.
4. Server verifies JWT (`sub`, `aud: "/retrieve"`, `exp`), then returns stored items as a paginated JSON list of item IDs and an optional opaque `next_cursor` token for pagination.

Each item is saved and decrypted locally. To fetch more items, pass the `next_cursor` value as the `cursor` query parameter in the next request. The format and contents of the cursor are not specified and may change; treat it as an opaque string.

### `notify`

Register a Telegram hook:

```sh
deadrop.sh notify -i id_x25519 -t "@alice" # or numeric user ID
```

1. Client calls `POST /challenge` with `{ "pubkey": "", "scope": "notify", "telegram": "" }` → returns `{ "ciphertext": "" }`
2. Client decrypts ciphertext to get the JWT.
3. Client calls `POST /notify` with `Authorization: Bearer ` header.
4. Server verifies JWT (`sub`, `aud: "/notify"`, `exp`, `telegram`), then registers the hook.

---

## Server API

All endpoints expect and return JSON unless otherwise specified. Client decrypts the JWT challenge received from `/challenge`.

### `POST /upload`

* **Headers**: `X-PubKey: `
* **Body**: raw ciphertext
* **Response**: `201 Created` on success

Server stores each blob with its associated public key and timestamp.

### `POST /challenge`

* **Body**: `{ "pubkey": "", "scope": "", "telegram"?: "" }`
* **Response**: `{ "ciphertext": "" }`

Server creates a JWT with:

```json
{
"sub": "",
"aud": "/", // e.g., "/retrieve" or "/notify"
"iat": ,
"exp": ,
"telegram"?: "" // Included only for notify scope
}
```

Then signs (HS256) and encrypts via age for the user’s `pubkey`.

### `POST /retrieve`

* **Headers**: `Authorization: Bearer `
* **Query**: `cursor` (optional, opaque server-issued token for pagination)
* **Response**: `{ "items": [ "", ... ], "next_cursor": "" }`

Server verifies JWT signature, `aud: "/retrieve"`, `exp`, matches `sub` to an existing user with data, then returns a paginated list of item IDs and a server-issued opaque cursor for pagination. The client must not attempt to construct or modify the cursor value.

### `POST /notify`

* **Headers**: `Authorization: Bearer `
* **Response**: `200 OK`

Server verifies JWT signature, `aud: "/notify"`, `exp`, and the presence of the `telegram` claim. It then registers the Telegram ID from the claim for push notifications on future uploads associated with the `sub` (pubkey).

---

## Security Considerations

* **Single keypair**: only X25519 used for both encryption and proof-of-possession.
* **Stateless Authentication**: Encrypted JWT carries necessary claims (`iat`/`exp`/`sub`/`aud`/`telegram`); server verifies the token presented in the `Authorization` header. No server-side session state needed after issuing the challenge.
* **Short TTL** (default 5 minutes) on JWT prevents replay attacks.
* **TLS** protects headers (including `Authorization`) and bodies in transit.
* **Scope Claim**: The `aud` (audience) claim in the JWT ensures a token issued for one purpose (e.g., `retrieve`) cannot be used for another (e.g., `notify`).

---

## License

MIT © Joe Fang