mirror of
https://github.com/mautrix/signal.git
synced 2026-09-17 08:12:05 -04:00
554 lines
21 KiB
Protocol Buffer
554 lines
21 KiB
Protocol Buffer
/*
|
|
* Copyright 2024 Signal Messenger, LLC
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
syntax = "proto3";
|
|
|
|
option java_multiple_files = true;
|
|
|
|
package org.signal.chat.backup;
|
|
|
|
import "google/protobuf/empty.proto";
|
|
import "org/signal/chat/common.proto";
|
|
import "org/signal/chat/errors.proto";
|
|
import "org/signal/chat/require.proto";
|
|
import "org/signal/chat/tag.proto";
|
|
|
|
// Service for backup operations that require account authentication.
|
|
//
|
|
// Most actual backup operations operate on the backup-id and cannot be linked
|
|
// to the caller's account, but setting up anonymous credentials and changing
|
|
// backup tier requires account authentication.
|
|
service Backups {
|
|
option (require.auth) = AUTH_ONLY_AUTHENTICATED;
|
|
|
|
// Set (blinded) backup-id(s) for the account.
|
|
//
|
|
// Each account may have a single active backup-id for each credential type
|
|
// that can be used to store and retrieve backups. Once the backup-id is set,
|
|
// BackupAuthCredentials can be generated using GetBackupAuthCredentials.
|
|
//
|
|
// The blinded backup-id and the key-pair used to blind it must be derived
|
|
// from a recoverable secret.
|
|
//
|
|
// At least one of the credential types must be set on the request.
|
|
// Only the primary device can set a blinded backup-id.
|
|
rpc SetBackupId(SetBackupIdRequest) returns (SetBackupIdResponse) {}
|
|
|
|
// Redeem a receipt acquired from /v1/subscription/{subscriberId}/receipt_credentials
|
|
// to mark the account as eligible for the paid backup tier.
|
|
//
|
|
// After successful redemption, subsequent requests to
|
|
// GetBackupAuthCredentials will return credentials with the level on the
|
|
// provided receipt until the expiration time on the receipt.
|
|
rpc RedeemReceipt(RedeemReceiptRequest) returns (RedeemReceiptResponse) {}
|
|
|
|
// After setting a blinded backup-id with PUT /v1/archives/, this fetches
|
|
// credentials that can be used to perform operations against that backup-id.
|
|
// Clients may (and should) request up to 7 days of credentials at a time.
|
|
//
|
|
// The redemption_start and redemption_end seconds must be UTC day aligned, and
|
|
// must not span more than 7 days.
|
|
//
|
|
// Each credential contains a receipt level which indicates the backup level
|
|
// the credential is good for. If the account has paid backup access that
|
|
// expires at some point in the provided redemption window, credentials with
|
|
// redemption times after the expiration may be on a lower backup level.
|
|
//
|
|
// Clients must validate the receipt level on the credential matches a known
|
|
// receipt level before using it.
|
|
rpc GetBackupAuthCredentials(GetBackupAuthCredentialsRequest) returns (GetBackupAuthCredentialsResponse) {}
|
|
}
|
|
|
|
message SetBackupIdRequest {
|
|
// A BackupAuthCredentialRequest containing a blinded encrypted backup-id,
|
|
// encoded in standard padded base64. This backup-id should be used for
|
|
// message backups only, and must have the message backup type set on the
|
|
// credential. If absent, the message credential request will not be updated.
|
|
bytes messages_backup_auth_credential_request = 1;
|
|
|
|
// A BackupAuthCredentialRequest containing a blinded encrypted backup-id,
|
|
// encoded in standard padded base64. This backup-id should be used for
|
|
// media only, and must have the media type set on the credential. If absent,
|
|
// the media credential request will not be updated.
|
|
bytes media_backup_auth_credential_request = 2;
|
|
}
|
|
|
|
message SetBackupIdResponse {}
|
|
|
|
|
|
message RedeemReceiptRequest {
|
|
// Presentation for a previously acquired receipt, serialized with libsignal
|
|
bytes presentation = 1;
|
|
}
|
|
|
|
message RedeemReceiptResponse {
|
|
oneof response {
|
|
// The receipt was successfully redeemed
|
|
google.protobuf.Empty success = 1;
|
|
|
|
// The target account does not have a backup-id commitment
|
|
errors.FailedPrecondition account_missing_commitment = 2 [(tag.reason) = "account_missing_commitment"];
|
|
|
|
// The provided receipt presentation was malformed or expired
|
|
errors.FailedPrecondition invalid_receipt = 3 [(tag.reason) = "invalid_receipt"];
|
|
}
|
|
}
|
|
|
|
message GetBackupAuthCredentialsRequest {
|
|
// The redemption time for the first credential. This must be a day-aligned
|
|
// seconds since epoch in UTC.
|
|
int64 redemption_start = 1 [(require.range).min = 1];
|
|
|
|
// The redemption time for the last credential. This must be a day-aligned
|
|
// seconds since epoch in UTC. The span between redemptionStart and
|
|
// redemptionEnd must not exceed 7 days.
|
|
int64 redemption_stop = 2 [(require.range).min = 1];
|
|
}
|
|
|
|
message GetBackupAuthCredentialsResponse {
|
|
message Credentials {
|
|
// The requested message backup ZkCredentials indexed by the start of their
|
|
// validity period. The smallest key should be for the requested
|
|
// redemption_start, the largest for the requested redemption_end.
|
|
map<int64, common.ZkCredential> message_credentials = 1;
|
|
|
|
// The requested media backup ZkCredentials indexed by the start of their
|
|
// validity period. The smallest key should be for the requested
|
|
// redemption_start, the largest for the requested redemption_end.
|
|
map<int64, common.ZkCredential> media_credentials = 2;
|
|
}
|
|
|
|
// The requested credentials. If absent, there was no existing blinded
|
|
// backup id associated with the provided account.
|
|
Credentials credentials = 1;
|
|
}
|
|
|
|
// Service for backup operations with anonymous credentials
|
|
//
|
|
// This service never requires account authentication. It instead requires a
|
|
// backup-id authenticated with an anonymous credential that cannot be linked
|
|
// to the account.
|
|
//
|
|
// To register an anonymous credential:
|
|
//
|
|
// 1. Set a backup-id on the authenticated channel via Backups::SetBackupId
|
|
// 2. Retrieve BackupAuthCredentials via Backups::GetBackupAuthCredentials
|
|
// 3. Generate a key pair and set the public key via
|
|
// BackupsAnonymous::SetPublicKey
|
|
//
|
|
// Unless otherwise noted, requests for this service require a
|
|
// SignedPresentation, which includes:
|
|
//
|
|
// - a presentation generated from a BackupAuthCredential issued by
|
|
// GetBackupAuthCredentials
|
|
// - a signature of that presentation using the private key of a key pair
|
|
// previously set with SetPublicKey.
|
|
service BackupsAnonymous {
|
|
option (require.auth) = AUTH_ONLY_ANONYMOUS;
|
|
|
|
// Retrieve credentials used to read objects stored on the backup cdn
|
|
rpc GetCdnCredentials(GetCdnCredentialsRequest) returns (GetCdnCredentialsResponse) {}
|
|
|
|
// Retrieve credentials used to interact with the SecureValueRecoveryB service
|
|
rpc GetSvrBCredentials(GetSvrBCredentialsRequest) returns (GetSvrBCredentialsResponse) {}
|
|
|
|
// Retrieve information about the currently stored message backup
|
|
rpc GetMessageBackupInfo(GetBackupInfoRequest) returns (GetMessageBackupInfoResponse) {}
|
|
|
|
// Retrieve information about the currently stored media backup
|
|
rpc GetMediaBackupInfo(GetBackupInfoRequest) returns (GetMediaBackupInfoResponse) {}
|
|
|
|
// Permanently set the public key of an ED25519 key-pair for the backup-id.
|
|
// All requests (including this one!) must sign their BackupAuthCredential
|
|
// presentations with the private key corresponding to the provided public key.
|
|
rpc SetPublicKey(SetPublicKeyRequest) returns (SetPublicKeyResponse) {}
|
|
|
|
// Refresh the backup, indicating that the backup is still active. Clients
|
|
// must periodically upload new backups or perform a refresh. If a backup has
|
|
// not been active for 30 days, it may be deleted.
|
|
rpc Refresh(RefreshRequest) returns (RefreshResponse) {}
|
|
|
|
// Retrieve an upload form that can be used to perform a resumable upload
|
|
rpc GetUploadForm(GetUploadFormRequest) returns (GetUploadFormResponse) {}
|
|
|
|
// Copy and re-encrypt media from the attachments cdn into the backup cdn.
|
|
// The original, already encrypted, attachments will be encrypted with the
|
|
// provided key material before being copied.
|
|
//
|
|
// The copy operation is not atomic and responses will be returned as copy
|
|
// operations complete with detailed information about the outcome. If an
|
|
// error is encountered, not all requests may be reflected in the responses.
|
|
//
|
|
// On retries, a particular destination media id must not be reused with a
|
|
// different source media id or different encryption parameters.
|
|
//
|
|
// The response stream may be closed with STREAM_CLOSED error reason. In this
|
|
// case, a BackupStreamClosed message will be present in the error details.
|
|
rpc CopyMedia(CopyMediaRequest) returns (stream CopyMediaResponse) {}
|
|
|
|
// Retrieve a page of media objects stored for this backup-id. A client may
|
|
// have previously stored media objects that are no longer referenced in their
|
|
// current backup. To reclaim storage space used by these orphaned objects,
|
|
// perform a list operation and remove any unreferenced media objects
|
|
// via DeleteMedia.
|
|
rpc ListMedia(ListMediaRequest) returns (ListMediaResponse) {}
|
|
|
|
// Delete media objects stored with this backup-id. Streams the locations of
|
|
// media items back when the item has successfully been removed.
|
|
//
|
|
// The response stream may be closed with STREAM_CLOSED error reason. In this
|
|
// case, a BackupStreamClosed message will be present in the error details.
|
|
rpc DeleteMedia(DeleteMediaRequest) returns (stream DeleteMediaResponse) {}
|
|
|
|
// Delete all backup metadata, objects, and stored public key. To use
|
|
// backups again, a public key must be resupplied.
|
|
rpc DeleteAll(DeleteAllRequest) returns (DeleteAllResponse) {}
|
|
}
|
|
|
|
message SignedPresentation {
|
|
// Presentation of a BackupAuthCredential previously retrieved from
|
|
// GetBackupAuthCredentials on the authenticated channel
|
|
bytes presentation = 1 [(require.nonEmpty) = true];
|
|
|
|
// The presentation signed with the private key corresponding to the public
|
|
// key set with SetPublicKey
|
|
bytes presentation_signature = 2 [(require.nonEmpty) = true];
|
|
}
|
|
|
|
message SetPublicKeyRequest {
|
|
SignedPresentation signed_presentation = 1;
|
|
|
|
// The public key, serialized in libsignal's elliptic-curve public key format.
|
|
bytes public_key = 2 [(require.nonEmpty) = true];
|
|
}
|
|
|
|
message SetPublicKeyResponse {
|
|
oneof response {
|
|
// The public key was successfully set
|
|
google.protobuf.Empty success = 1;
|
|
|
|
// The provided backup auth credential presentation could not be
|
|
// authenticated. Either, the presentation could not be verified, or
|
|
// the public key signature was invalid, or there is no backup associated
|
|
// with the backup-id in the presentation.
|
|
//
|
|
// This may also be returned if there was an existing public key and the
|
|
// provided public key did not match.
|
|
errors.FailedZkAuthentication failed_authentication = 2 [(tag.reason) = "failed_authentication"];
|
|
}
|
|
}
|
|
|
|
message GetCdnCredentialsRequest {
|
|
SignedPresentation signed_presentation = 1;
|
|
uint32 cdn = 2;
|
|
}
|
|
message GetCdnCredentialsResponse {
|
|
message CdnCredentials {
|
|
map<string, string> headers = 1;
|
|
}
|
|
oneof response {
|
|
// Headers to include with requests to the read from the backup CDN. Includes
|
|
// time limited read-only credentials.
|
|
CdnCredentials cdn_credentials = 1;
|
|
|
|
// The provided backup auth credential presentation could not be
|
|
// authenticated. Either, the presentation could not be verified, or
|
|
// the public key signature was invalid, or there is no backup associated
|
|
// with the backup-id in the presentation.
|
|
errors.FailedZkAuthentication failed_authentication = 2 [(tag.reason) = "failed_authentication"];
|
|
}
|
|
}
|
|
|
|
message GetSvrBCredentialsRequest {
|
|
SignedPresentation signed_presentation = 1;
|
|
}
|
|
|
|
message GetSvrBCredentialsResponse {
|
|
message SvrBCredentials {
|
|
// A username that can be presented to authenticate with SVRB
|
|
string username = 1;
|
|
|
|
// A password that can be presented to authenticate with SVRB
|
|
string password = 2;
|
|
}
|
|
|
|
oneof response {
|
|
SvrBCredentials svrb_credentials = 1;
|
|
|
|
// The provided backup auth credential presentation could not be
|
|
// authenticated. Either, the presentation could not be verified, or
|
|
// the public key signature was invalid, or there is no backup associated
|
|
// with the backup-id in the presentation.
|
|
errors.FailedZkAuthentication failed_authentication = 2 [(tag.reason) = "failed_authentication"];
|
|
}
|
|
}
|
|
|
|
message GetBackupInfoRequest {
|
|
SignedPresentation signed_presentation = 1;
|
|
}
|
|
message GetMessageBackupInfoResponse {
|
|
message MessageBackupInfo {
|
|
// The base directory of your backup data on the cdn. Always non-empty, even
|
|
// if a backup has not actually been stored to the cdn. If a backup was
|
|
// previously uploaded and has not expired, it can be found in the returned
|
|
// cdn at /backup_dir/backup_name.
|
|
string backup_dir = 1;
|
|
|
|
// The CDN type where the message backup is stored. Media may be stored
|
|
// elsewhere.
|
|
uint32 cdn = 2;
|
|
|
|
// The location of the message backup on the cdn. Always non-empty, even
|
|
// if a backup has not actually been stored to the cdn. If a backup was
|
|
// previously uploaded and has not expired, it can be found in the returned
|
|
// cdn at /backup_dir/backup_name.
|
|
string backup_name = 3;
|
|
}
|
|
|
|
oneof response {
|
|
MessageBackupInfo backup_info = 1;
|
|
|
|
// The provided backup auth credential presentation could not be
|
|
// authenticated. Either, the presentation could not be verified, or
|
|
// the public key signature was invalid, or there is no backup associated
|
|
// with the backup-id in the presentation.
|
|
errors.FailedZkAuthentication failed_authentication = 2 [(tag.reason) = "failed_authentication"];
|
|
}
|
|
}
|
|
message GetMediaBackupInfoResponse {
|
|
message MediaBackupInfo {
|
|
// The base directory of your backup data on the cdn. Always non-empty, even
|
|
// if no media has been stored to the cdn or the credential is for a tier
|
|
// that does not support media.
|
|
string backup_dir = 1;
|
|
|
|
// The prefix path component for media objects on a cdn. Stored media for a
|
|
// media_id can be found at /backup_dir/media_dir/media_id, where the
|
|
// media_id is encoded in unpadded url-safe base64. Always non-empty, even
|
|
// if no media has been stored to the cdn or the credential is for a tier
|
|
// that does not support media.
|
|
string media_dir = 2;
|
|
|
|
// The amount of space used to store media
|
|
uint64 used_space = 3;
|
|
}
|
|
|
|
oneof response {
|
|
MediaBackupInfo backup_info = 1;
|
|
|
|
// The provided backup auth credential presentation could not be
|
|
// authenticated. Either, the presentation could not be verified, or
|
|
// the public key signature was invalid, or there is no backup associated
|
|
// with the backup-id in the presentation.
|
|
errors.FailedZkAuthentication failed_authentication = 2 [(tag.reason) = "failed_authentication"];
|
|
}
|
|
}
|
|
|
|
message RefreshRequest {
|
|
SignedPresentation signed_presentation = 1;
|
|
}
|
|
message RefreshResponse {
|
|
oneof response {
|
|
// The backup was successfully refreshed
|
|
google.protobuf.Empty success = 1;
|
|
|
|
// The provided backup auth credential presentation could not be
|
|
// authenticated. Either, the presentation could not be verified, or
|
|
// the public key signature was invalid, or there is no backup associated
|
|
// with the backup-id in the presentation.
|
|
errors.FailedZkAuthentication failed_authentication = 2 [(tag.reason) = "failed_authentication"];
|
|
}
|
|
}
|
|
|
|
message GetUploadFormRequest {
|
|
SignedPresentation signed_presentation = 1;
|
|
|
|
message MessagesUploadType {}
|
|
message MediaUploadType {}
|
|
oneof upload_type {
|
|
// Retrieve an upload form that can be used to perform a resumable upload of
|
|
// a message backup. The finished upload will be available on the backup cdn.
|
|
MessagesUploadType messages = 2;
|
|
|
|
// Retrieve an upload form for a temporary location that can be used to
|
|
// perform a resumable upload of an attachment. After uploading, the
|
|
// attachment can be copied into the backup via CopyMedia.
|
|
//
|
|
// Behaves identically to the account authenticated version at /attachments.
|
|
MediaUploadType media = 3;
|
|
}
|
|
|
|
// The length of the attachment for the requested upload form. Uploads
|
|
// performed with this form will be limited to the provided length.
|
|
uint64 uploadLength = 4 [(require.range) = {min: 1}];
|
|
}
|
|
message GetUploadFormResponse {
|
|
oneof response {
|
|
common.UploadForm upload_form = 1;
|
|
|
|
// The provided backup auth credential presentation could not be
|
|
// authenticated. Either, the presentation could not be verified, or
|
|
// the public key signature was invalid, or there is no backup associated
|
|
// with the backup-id in the presentation.
|
|
errors.FailedZkAuthentication failed_authentication = 2 [(tag.reason) = "failed_authentication"];
|
|
|
|
// The request size was larger than the maximum supported upload size. The
|
|
// maximum upload size is subject to change and is governed by
|
|
// `global.attachments.maxBytes`
|
|
errors.FailedPrecondition exceeds_max_upload_length = 3 [(tag.reason) = "oversize_upload"];
|
|
}
|
|
}
|
|
|
|
message CopyMediaItem {
|
|
// The attachment cdn of the object to copy into the backup
|
|
uint32 source_attachment_cdn = 1 [(require.range).min = 1, (require.range).max = 3];
|
|
|
|
// The attachment key of the object to copy into the backup
|
|
string source_key = 2 [(require.nonEmpty) = true, (require.base64url) = true];
|
|
|
|
// The length of the source attachment before the encryption applied by the
|
|
// copy operation
|
|
uint64 object_length = 3;
|
|
|
|
// media_id to copy on to the backup CDN
|
|
bytes media_id = 4 [(require.exactlySize) = 15];
|
|
|
|
// A 32-byte key for the MAC
|
|
bytes hmac_key = 5 [(require.exactlySize) = 32];
|
|
|
|
// A 32-byte encryption key for AES
|
|
bytes encryption_key = 6 [(require.exactlySize) = 32];
|
|
}
|
|
|
|
message CopyMediaRequest {
|
|
SignedPresentation signed_presentation = 1;
|
|
|
|
// Items to copy
|
|
repeated CopyMediaItem items = 2 [(require.size) = {min: 1, max: 1000}];
|
|
}
|
|
|
|
message CopyMediaResponse {
|
|
message SourceNotFound {}
|
|
message WrongSourceLength {}
|
|
message OutOfSpace {}
|
|
message CopySuccess {
|
|
// The backup cdn where this media object is stored
|
|
uint32 cdn = 1;
|
|
}
|
|
|
|
// The 15-byte media_id from the corresponding CopyMediaItem in the request
|
|
bytes media_id = 1;
|
|
|
|
oneof response {
|
|
// The media item was successfully copied into the backup
|
|
CopySuccess success = 2;
|
|
|
|
// The source object was not found
|
|
SourceNotFound source_not_found = 3 [(tag.reason) = "source_not_found"];
|
|
|
|
// The provided object length was incorrect
|
|
WrongSourceLength wrong_source_length = 4 [(tag.reason) = "wrong_source_length"];
|
|
|
|
// All media capacity has been consumed. Free some space to continue.
|
|
OutOfSpace out_of_space = 5 [(tag.reason) = "out_of_space"];
|
|
}
|
|
}
|
|
|
|
// The reason why a media stream RPC is being prematurely closed by the server.
|
|
message BackupStreamClosed {
|
|
oneof reason {
|
|
// The provided backup auth credential presentation could not be
|
|
// authenticated. Either, the presentation could not be verified, or
|
|
// the public key signature was invalid, or there is no backup associated
|
|
// with the backup-id in the presentation.
|
|
errors.FailedZkAuthentication failed_authentication = 1 [(tag.reason) = "failed_authentication"];
|
|
}
|
|
}
|
|
|
|
message ListMediaRequest {
|
|
SignedPresentation signed_presentation = 1;
|
|
|
|
// A cursor returned by a previous call to ListMedia, absent on the first call
|
|
optional string cursor = 2;
|
|
|
|
// If provided, the maximum number of entries to return in a page. If absent,
|
|
// a server-chosen default is used.
|
|
optional uint32 limit = 3 [(require.range) = {min: 1, max: 10000}];
|
|
}
|
|
|
|
message ListMediaResponse {
|
|
message ListEntry {
|
|
// The backup cdn where this media object is stored
|
|
uint32 cdn = 1;
|
|
// The media_id of the object
|
|
bytes media_id = 2;
|
|
// The length of the object in bytes
|
|
uint64 length = 3;
|
|
}
|
|
|
|
message ListResult {
|
|
|
|
// A page of media objects stored for this backup ID
|
|
repeated ListEntry page = 1;
|
|
|
|
// The base directory of the backup data on the cdn. The stored media can be
|
|
// found at /backup_dir/media_dir/media_id, where the media_id is encoded with
|
|
// unpadded url-safe base64.
|
|
string backup_dir = 2;
|
|
|
|
// The prefix path component for the media objects. The stored media for
|
|
// media_id can be found at /backup_dir/media_dir/media_id, where the media_id
|
|
// is encoded with unpadded url-safe base64.
|
|
string media_dir = 3;
|
|
|
|
// If set, the cursor value to pass to the next list request to continue
|
|
// listing. If absent, all objects have been listed
|
|
optional string cursor = 4;
|
|
}
|
|
|
|
oneof response {
|
|
ListResult list_result = 1;
|
|
|
|
// The provided backup auth credential presentation could not be
|
|
// authenticated. Either, the presentation could not be verified, or
|
|
// the public key signature was invalid, or there is no backup associated
|
|
// with the backup-id in the presentation.
|
|
errors.FailedZkAuthentication failed_authentication = 2 [(tag.reason) = "failed_authentication"];
|
|
}
|
|
}
|
|
|
|
message DeleteAllRequest {
|
|
SignedPresentation signed_presentation = 1;
|
|
}
|
|
message DeleteAllResponse {
|
|
oneof response {
|
|
// The backup was successfully scheduled for deletion
|
|
google.protobuf.Empty success = 1;
|
|
|
|
// The provided backup auth credential presentation could not be
|
|
// authenticated. Either, the presentation could not be verified, or
|
|
// the public key signature was invalid, or there is no backup associated
|
|
// with the backup-id in the presentation.
|
|
errors.FailedZkAuthentication failed_authentication = 2 [(tag.reason) = "failed_authentication"];
|
|
}
|
|
}
|
|
|
|
message DeleteMediaItem {
|
|
// The backup cdn where this media object is stored
|
|
uint32 cdn = 1;
|
|
|
|
// The media_id of the object to delete
|
|
bytes media_id = 2 [(require.exactlySize) = 15];
|
|
}
|
|
|
|
message DeleteMediaRequest {
|
|
SignedPresentation signed_presentation = 1;
|
|
|
|
repeated DeleteMediaItem items = 2 [(require.size) = {min: 1, max: 1000}];
|
|
}
|
|
|
|
message DeleteMediaResponse {
|
|
DeleteMediaItem deleted_item = 1;
|
|
}
|