proton-sdk 0.1.2

Core Proton account/session/crypto primitives for the Rust Proton SDK
Documentation

proton-sdk-rs

A pure-Rust reimplementation of the Proton Drive SDK. No dependency on the official NativeAOT core — it talks to the Proton Drive API and does the OpenPGP crypto directly. The canonical reference is the upstream C# implementation; behavior is matched to it where the protocol is unclear.

Unofficial, third-party project. Not affiliated with or endorsed by Proton. If you build on it, follow the operational requirements in the upstream SDK README (honest x-pm-appversion, event-based sync, no Proton branding, etc.).

Status

Milestone 2 — authenticated read/write. Implemented:

  • Core / HTTP: typed IDs, API response envelope, config, reqwest client with x-pm-uid / x-pm-appversion / bearer auth, transparent 401 refresh, retry/backoff (408/429/502/503/504 + transient transport, Retry-After + jitter), and per-request telemetry.
  • Session: SRP-6a password login (begin), TOTP 2FA (apply_second_factor_code), scope refresh, resume from tokens, end.
  • Account: user keys, address keys, bcrypt key-passphrase derivation, public-key resolution for authorship verification (core/v4/keys/all).
  • Drive read: get_my_files_folder, get_node, enumerate_folder_children, enumerate_nodes, enumerate_trash, with share/node decryption and recursive parent-key resolution.
  • Download: download_file / download_file_to (streaming), thumbnails, content-manifest integrity + non-fatal signature verification.
  • Upload: upload_file / upload_file_from (streaming Read), new revisions, caller-supplied thumbnails, and AEAD blocks (SEIPDv2 / AES-256-GCM) alongside legacy SEIPDv1.
  • Folder / node ops: create, rename, trash, restore, delete, empty-trash, and same-volume move_node.
  • Events: main_volume_id, latest_event_id, enumerate_events.
  • Photos: ProtonPhotosClient — timeline, photo get/download, upload.
  • Caching: pluggable entity cache (with_entity_cache); secrets stay in-memory.
  • Telemetry: pluggable Telemetry observer (with_telemetry).

Not yet implemented: cross-volume move (needs NewShareID + re-signing), sharing, signature-verification enforcement (it is non-fatal metadata).

All crypto paths have offline round-trip tests, and the read/write surface (upload/download round-trips, AEAD + streaming + thumbnails, enumeration, folder/node ops, move, and the event feed) is covered by a live integration test suite validated against a real Proton account — see crates/proton-drive-rs/tests/live_*.rs.

Workspace

Crate Purpose
crates/proton-sdk Core account/session/crypto (Proton.Sdk analogue)
crates/proton-drive-rs High-level Drive client (Proton.Drive.Sdk analogue)

OpenPGP is provided by the pgp crate (rPGP 0.16).

The mailbox-password requirement

Proton's key model is layered, so read-only decryption still needs the user's mailbox (data) password, not just session tokens:

mailbox password + key salt --bcrypt--> key passphrase
  -> unlock user keys -> decrypt address-key token -> unlock address key
  -> decrypt share/node passphrase -> unlock node key -> decrypt names

ProtonApiSession::begin / resume handle the tokens; ProtonDriveClient::new takes the mailbox password.

Usage

SRP login (with 2FA), then read the my-files root:

use proton_sdk::config::ProtonClientConfiguration;
use proton_sdk::session::ProtonApiSession;
use proton_drive_rs::ProtonDriveClient;

async fn run() -> proton_sdk::error::Result<()> {
    let config = ProtonClientConfiguration::new("external-drive-myapp@0.1.0-alpha");

    let mut session =
        ProtonApiSession::begin(config, "user@proton.me", b"mailbox-password").await?;
    if session.is_waiting_for_second_factor() {
        session.apply_second_factor_code("123456").await?;
    }

    let drive = ProtonDriveClient::new(&session, b"mailbox-password".to_vec());
    let root = drive.get_my_files_folder().await?;
    for child in drive.enumerate_folder_children(&root.uid).await? {
        println!("{} ({:?})", child.name, child.kind);
    }
    Ok(())
}

Already have tokens? Use ProtonApiSession::resume(config, ResumeParameters {..}) instead of begin.

A live smoke test lives in crates/proton-drive-rs/examples/live_login.rs (reads username / password from a repo-root .env, TOTP from PROTON_TOTP_SECRET):

PROTON_TOTP_SECRET=... cargo run -p proton-drive-rs --example live_login

Build & test

cargo build   # 0 warnings / 0 errors
cargo test    # offline crypto + derivation round-trip tests

The crates/proton-drive-rs/tests/live_*.rs suite runs the read/write surface against a real account. These tests are #[ignore]d by default (they need credentials) and each cleans up after itself. With .env (username / password) and PROTON_TOTP_SECRET set:

# all live tests, single-threaded (shared test account)
PROTON_TOTP_SECRET=... cargo test -p proton-drive-rs --test 'live_*' \
  -- --ignored --nocapture --test-threads=1

Note: AEAD upload (SEIPDv2) is server-gated behind the DriveCryptoEncryptBlocksWithPgpAead feature flag; that test no-ops when the flag is off for the account.

License

MIT, matching upstream. The MIT license covers this source only; use of Proton's hosted services remains subject to Proton's terms.