mirror of
https://github.com/signalapp/libsignal.git
synced 2026-04-25 17:25:18 +02:00
Add gRPC support for getUploadForm()
This commit is contained in:
@@ -1,2 +1,4 @@
|
||||
v0.90.1
|
||||
|
||||
- Support gRPC for getUploadForm()
|
||||
|
||||
|
||||
@@ -1843,7 +1843,7 @@ export interface RegisterAccountResponse { readonly __type: unique symbol; }
|
||||
export interface RegistrationAccountAttributes { readonly __type: unique symbol; }
|
||||
export interface BackupStoreResponse { readonly __type: unique symbol; }
|
||||
export interface BackupRestoreResponse { readonly __type: unique symbol; }
|
||||
export const NetRemoteConfigKeys = ['chatRequestConnectionCheckTimeoutMillis', 'useH2ForUnauthChat', 'useH2ForAuthChat', 'grpc.AccountsAnonymousLookupUsernameHash', 'grpc.AccountsAnonymousLookupUsernameLink.2', 'grpc.AccountsAnonymousCheckAccountExistence.2', 'grpc.MessagesAnonymousSendMultiRecipientMessage.2', ] as const;
|
||||
export const NetRemoteConfigKeys = ['chatRequestConnectionCheckTimeoutMillis', 'useH2ForUnauthChat', 'useH2ForAuthChat', 'grpc.AccountsAnonymousLookupUsernameHash', 'grpc.AccountsAnonymousLookupUsernameLink.2', 'grpc.AccountsAnonymousCheckAccountExistence.2', 'grpc.MessagesAnonymousSendMultiRecipientMessage.2', 'grpc.AttachmentsGetUploadForm', ] as const;
|
||||
export interface TokioAsyncContext { readonly __type: unique symbol; }
|
||||
export interface ConnectionManager { readonly __type: unique symbol; }
|
||||
export interface ConnectionProxyConfig { readonly __type: unique symbol; }
|
||||
|
||||
@@ -116,6 +116,7 @@ pub enum RemoteConfigKey {
|
||||
AccountsAnonymousLookupUsernameLink => "grpc.AccountsAnonymousLookupUsernameLink.2",
|
||||
AccountsAnonymousCheckAccountExistence => "grpc.AccountsAnonymousCheckAccountExistence.2",
|
||||
MessagesAnonymousSendMultiRecipientMessage => "grpc.MessagesAnonymousSendMultiRecipientMessage.2",
|
||||
AttachmentsGetUploadForm => "grpc.AttachmentsGetUploadForm",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,6 +296,7 @@ mod tests {
|
||||
// Add new services as they become relevant.
|
||||
let all_known_grpc_keys: HashSet<&str> = std::iter::empty()
|
||||
.chain(services::AccountsAnonymous::iter().map(|x| x.into()))
|
||||
.chain(services::Attachments::iter().map(|x| x.into()))
|
||||
.chain(services::KeysAnonymous::iter().map(|x| x.into()))
|
||||
.chain(services::MessagesAnonymous::iter().map(|x| x.into()))
|
||||
.collect();
|
||||
|
||||
@@ -11,6 +11,7 @@ use libsignal_net_grpc::proto::chat::backup::get_upload_form_request::{
|
||||
use libsignal_net_grpc::proto::chat::backup::{
|
||||
GetUploadFormRequest, GetUploadFormResponse, SignedPresentation, get_upload_form_response,
|
||||
};
|
||||
use libsignal_net_grpc::proto::chat::common;
|
||||
use libsignal_net_grpc::proto::chat::errors::{FailedPrecondition, FailedZkAuthentication};
|
||||
|
||||
use super::{GrpcServiceProvider, OverGrpc, log_and_send};
|
||||
@@ -105,7 +106,7 @@ impl TryFrom<GetUploadFormResponse> for UploadForm {
|
||||
})?;
|
||||
|
||||
match outcome {
|
||||
Outcome::UploadForm(get_upload_form_response::UploadForm {
|
||||
Outcome::UploadForm(common::UploadForm {
|
||||
cdn,
|
||||
key,
|
||||
headers,
|
||||
@@ -189,7 +190,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test_case(ok(GetUploadFormResponse {
|
||||
outcome: Some(get_upload_form_response::Outcome::UploadForm(get_upload_form_response::UploadForm {
|
||||
outcome: Some(get_upload_form_response::Outcome::UploadForm(common::UploadForm {
|
||||
cdn: 123,
|
||||
key: "abcde".to_owned(),
|
||||
headers: HashMap::from_iter([
|
||||
@@ -251,7 +252,7 @@ mod test {
|
||||
}
|
||||
|
||||
#[test_case(ok(GetUploadFormResponse {
|
||||
outcome: Some(get_upload_form_response::Outcome::UploadForm(get_upload_form_response::UploadForm {
|
||||
outcome: Some(get_upload_form_response::Outcome::UploadForm(common::UploadForm {
|
||||
cdn: 123,
|
||||
key: "abcde".to_owned(),
|
||||
headers: HashMap::from_iter([
|
||||
|
||||
@@ -3,25 +3,30 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
use std::convert::Infallible;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use itertools::Itertools as _;
|
||||
use libsignal_core::{DeviceId, ServiceId};
|
||||
use libsignal_net_grpc::proto::chat::attachments::attachments_client::AttachmentsClient;
|
||||
use libsignal_net_grpc::proto::chat::common::ServiceIdentifier;
|
||||
use libsignal_net_grpc::proto::chat::errors;
|
||||
use libsignal_net_grpc::proto::chat::messages::messages_anonymous_client::MessagesAnonymousClient;
|
||||
use libsignal_net_grpc::proto::chat::messages::{
|
||||
MismatchedDevices, MultiRecipientMessage, MultiRecipientMismatchedDevices,
|
||||
MultiRecipientSuccess, SendMultiRecipientMessageRequest, SendMultiRecipientMessageResponse,
|
||||
SendMultiRecipientStoryRequest, send_multi_recipient_message_response,
|
||||
};
|
||||
use libsignal_net_grpc::proto::chat::{attachments, common, errors};
|
||||
use libsignal_protocol::Timestamp;
|
||||
|
||||
use super::{GrpcServiceProvider, OverGrpc, log_and_send};
|
||||
use crate::api::messages::{
|
||||
MismatchedDeviceError, MultiRecipientMessageResponse, MultiRecipientSendAuthorization,
|
||||
MultiRecipientSendFailure, SealedSendFailure, SingleOutboundSealedSenderMessage,
|
||||
UserBasedSendAuthorization,
|
||||
SingleOutboundUnsealedMessage, UnsealedSendFailure, UserBasedSendAuthorization,
|
||||
};
|
||||
use crate::api::{RequestError, Unauth};
|
||||
use crate::api::{Auth, RequestError, Unauth, UploadForm};
|
||||
use crate::logging::Redact;
|
||||
|
||||
#[async_trait]
|
||||
@@ -208,8 +213,66 @@ impl std::fmt::Display for Redact<SendMultiRecipientMessageRequest> {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Redact<attachments::GetUploadFormRequest> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let Self(attachments::GetUploadFormRequest {}) = self;
|
||||
f.debug_struct("attachments::GetUploadFormRequest").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: GrpcServiceProvider> crate::api::messages::AuthenticatedChatApi<OverGrpc> for Auth<T> {
|
||||
async fn send_message(
|
||||
&self,
|
||||
_destination: ServiceId,
|
||||
_timestamp: Timestamp,
|
||||
_contents: &[SingleOutboundUnsealedMessage<'_>],
|
||||
_online_only: bool,
|
||||
_urgent: bool,
|
||||
) -> Result<(), RequestError<UnsealedSendFailure>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn send_sync_message(
|
||||
&self,
|
||||
_timestamp: Timestamp,
|
||||
_contents: &[SingleOutboundUnsealedMessage<'_>],
|
||||
_urgent: bool,
|
||||
) -> Result<(), RequestError<MismatchedDeviceError>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn get_upload_form(&self) -> Result<UploadForm, RequestError<Infallible>> {
|
||||
let mut attachments_service = AttachmentsClient::new(self.0.service());
|
||||
let request = attachments::GetUploadFormRequest {};
|
||||
let log_safe_description = Redact(&request).to_string();
|
||||
let attachments::GetUploadFormResponse { upload_form } =
|
||||
log_and_send("auth", &log_safe_description, || {
|
||||
attachments_service.get_upload_form(request)
|
||||
})
|
||||
.await?
|
||||
.into_inner();
|
||||
let common::UploadForm {
|
||||
cdn,
|
||||
key,
|
||||
headers,
|
||||
signed_upload_location,
|
||||
} = upload_form.ok_or_else(|| RequestError::Unexpected {
|
||||
log_safe: "GetUploadFormResponse missing upload form".to_string(),
|
||||
})?;
|
||||
Ok(UploadForm {
|
||||
cdn,
|
||||
key,
|
||||
headers: headers.into_iter().collect(),
|
||||
signed_upload_url: signed_upload_location,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use futures_util::FutureExt as _;
|
||||
use libsignal_core::{Aci, Pni, ServiceId};
|
||||
use libsignal_net_grpc::proto::chat::services;
|
||||
@@ -218,7 +281,7 @@ mod test {
|
||||
use uuid::{Uuid, uuid};
|
||||
|
||||
use super::*;
|
||||
use crate::api::messages::UnauthenticatedChatApi as _;
|
||||
use crate::api::messages::{AuthenticatedChatApi, UnauthenticatedChatApi as _};
|
||||
use crate::api::testutil::{SERIALIZED_GROUP_SEND_TOKEN, structurally_valid_group_send_token};
|
||||
use crate::grpc::testutil::{GrpcOverrideRequestValidator, RequestValidator, err, ok, req};
|
||||
|
||||
@@ -457,4 +520,46 @@ mod test {
|
||||
.expect("success");
|
||||
assert_eq!(unregistered_ids, &[] as &[ServiceId]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attachment_get_upload_form() {
|
||||
let validator = GrpcOverrideRequestValidator {
|
||||
message: services::Attachments::GetUploadForm.into(),
|
||||
validator: RequestValidator {
|
||||
expected: req(
|
||||
"/org.signal.chat.attachments.Attachments/GetUploadForm",
|
||||
attachments::GetUploadFormRequest {},
|
||||
),
|
||||
response: ok(attachments::GetUploadFormResponse {
|
||||
upload_form: Some(common::UploadForm {
|
||||
cdn: 2,
|
||||
key: "my key".to_string(),
|
||||
headers: HashMap::from_iter([
|
||||
("one".to_string(), "val1".to_string()),
|
||||
("two".to_string(), "val2".to_string()),
|
||||
]),
|
||||
signed_upload_location: "location".to_string(),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
};
|
||||
let mut upload_form = Auth(&validator)
|
||||
.get_upload_form()
|
||||
.now_or_never()
|
||||
.expect("sync")
|
||||
.expect("success");
|
||||
upload_form.headers.sort(); // HashMap is non-deterministic
|
||||
assert_eq!(
|
||||
upload_form,
|
||||
UploadForm {
|
||||
cdn: 2,
|
||||
key: "my key".to_string(),
|
||||
headers: vec![
|
||||
("one".to_string(), "val1".to_string()),
|
||||
("two".to_string(), "val2".to_string()),
|
||||
],
|
||||
signed_upload_url: "location".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ use super::{
|
||||
use crate::api::messages::{
|
||||
MismatchedDeviceError, MultiRecipientMessageResponse, MultiRecipientSendAuthorization,
|
||||
MultiRecipientSendFailure, SealedSendFailure, SingleOutboundSealedSenderMessage,
|
||||
SingleOutboundUnsealedMessage, UnsealedSendFailure, UserBasedSendAuthorization,
|
||||
SingleOutboundUnsealedMessage, UnauthenticatedChatApi, UnsealedSendFailure,
|
||||
UserBasedSendAuthorization,
|
||||
};
|
||||
use crate::api::{Auth, RequestError, Unauth, UploadForm};
|
||||
use crate::logging::Redact;
|
||||
@@ -121,7 +122,7 @@ struct SendMessageRequest<'a> {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: WsConnection> crate::api::messages::UnauthenticatedChatApi<OverWs> for Unauth<T> {
|
||||
impl<T: WsConnection> UnauthenticatedChatApi<OverWs> for Unauth<T> {
|
||||
async fn send_message(
|
||||
&self,
|
||||
destination: ServiceId,
|
||||
@@ -398,6 +399,11 @@ impl<T: WsConnection> crate::api::messages::AuthenticatedChatApi<OverWs> for Aut
|
||||
}
|
||||
|
||||
async fn get_upload_form(&self) -> Result<UploadForm, RequestError<Infallible>> {
|
||||
if let Some(grpc) =
|
||||
self.grpc_service_to_use_instead(services::Attachments::GetUploadForm.into())
|
||||
{
|
||||
return Auth(grpc).get_upload_form().await;
|
||||
}
|
||||
let response = self
|
||||
.send(
|
||||
"auth",
|
||||
@@ -557,7 +563,7 @@ mod test {
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::*;
|
||||
use crate::api::messages::{AuthenticatedChatApi as _, UnauthenticatedChatApi as _};
|
||||
use crate::api::messages::AuthenticatedChatApi as _;
|
||||
use crate::api::testutil::{
|
||||
SERIALIZED_GROUP_SEND_TOKEN, TEST_SELF_ACI, structurally_valid_group_send_token,
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ fn main() {
|
||||
"proto/google/rpc/error_details.proto",
|
||||
"proto/google/rpc/status.proto",
|
||||
"proto/org/signal/chat/account.proto",
|
||||
"proto/org/signal/chat/attachments.proto",
|
||||
"proto/org/signal/chat/backups.proto",
|
||||
"proto/org/signal/chat/calling.proto",
|
||||
"proto/org/signal/chat/call_quality.proto",
|
||||
|
||||
@@ -6,6 +6,7 @@ package org.signal.chat.account;
|
||||
|
||||
import "org/signal/chat/common.proto";
|
||||
import "org/signal/chat/errors.proto";
|
||||
import "org/signal/chat/require.proto";
|
||||
|
||||
// Provides methods for working with Signal accounts.
|
||||
service Accounts {
|
||||
@@ -87,7 +88,7 @@ message DeleteAccountResponse {
|
||||
|
||||
message SetRegistrationLockRequest {
|
||||
// The new registration lock secret for the authenticated account.
|
||||
bytes registration_lock = 1;
|
||||
bytes registration_lock = 1 [(require.exactlySize) = 32];
|
||||
}
|
||||
|
||||
message SetRegistrationLockResponse {
|
||||
@@ -100,8 +101,9 @@ message ClearRegistrationLockResponse {
|
||||
}
|
||||
|
||||
message ReserveUsernameHashRequest {
|
||||
// A prioritized list of username hashes to attempt to reserve.
|
||||
repeated bytes username_hashes = 1;
|
||||
// A prioritized list of username hashes to attempt to reserve. Each hash must
|
||||
// be exactly 32 bytes.
|
||||
repeated bytes username_hashes = 1 [(require.size) = {min: 1, max: 20}];
|
||||
}
|
||||
|
||||
message UsernameNotAvailable {}
|
||||
@@ -119,15 +121,15 @@ message ReserveUsernameHashResponse {
|
||||
|
||||
message ConfirmUsernameHashRequest {
|
||||
// The username hash to claim for the authenticated account.
|
||||
bytes username_hash = 1;
|
||||
bytes username_hash = 1 [(require.exactlySize) = 32];
|
||||
|
||||
// A zero-knowledge proof that the given username hash was generated by the
|
||||
// Signal username algorithm.
|
||||
bytes zk_proof = 2;
|
||||
bytes zk_proof = 2 [(require.nonEmpty) = true];
|
||||
|
||||
// The ciphertext of the chosen username for use in public-facing contexts
|
||||
// (e.g. links and QR codes).
|
||||
bytes username_ciphertext = 3;
|
||||
bytes username_ciphertext = 3 [(require.size) = {min: 1, max: 128}];
|
||||
}
|
||||
|
||||
message ConfirmUsernameHashResponse {
|
||||
@@ -160,7 +162,7 @@ message DeleteUsernameHashResponse {
|
||||
|
||||
message SetUsernameLinkRequest {
|
||||
// The username ciphertext for which to generate a new link handle.
|
||||
bytes username_ciphertext = 1;
|
||||
bytes username_ciphertext = 1 [(require.size) = {min: 1, max: 128}];
|
||||
|
||||
// If true and the account already had an encrypted username stored, the
|
||||
// existing link handle will be reused. Otherwise a new link handle will be
|
||||
@@ -214,7 +216,7 @@ message SetDiscoverableByPhoneNumberResponse {
|
||||
|
||||
message SetRegistrationRecoveryPasswordRequest {
|
||||
// The new registration recovery password for the authenticated account.
|
||||
bytes registration_recovery_password = 1;
|
||||
bytes registration_recovery_password = 1 [(require.exactlySize) = 32];
|
||||
}
|
||||
|
||||
message SetRegistrationRecoveryPasswordResponse {
|
||||
@@ -233,7 +235,7 @@ message CheckAccountExistenceResponse {
|
||||
|
||||
message LookupUsernameHashRequest {
|
||||
// A 32-byte username hash for which to find an account.
|
||||
bytes username_hash = 1;
|
||||
bytes username_hash = 1 [(require.exactlySize) = 32];
|
||||
}
|
||||
|
||||
message LookupUsernameHashResponse {
|
||||
@@ -249,7 +251,7 @@ message LookupUsernameHashResponse {
|
||||
message LookupUsernameLinkRequest {
|
||||
// The link handle for which to find an encrypted username. Link handles are
|
||||
// 16-byte representations of UUIDs.
|
||||
bytes username_link_handle = 1;
|
||||
bytes username_link_handle = 1 [(require.exactlySize) = 16];
|
||||
}
|
||||
|
||||
message LookupUsernameLinkResponse {
|
||||
|
||||
25
rust/net/grpc/proto/org/signal/chat/attachments.proto
Normal file
25
rust/net/grpc/proto/org/signal/chat/attachments.proto
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
option java_multiple_files = true;
|
||||
|
||||
package org.signal.chat.attachments;
|
||||
|
||||
import "org/signal/chat/common.proto";
|
||||
import "org/signal/chat/require.proto";
|
||||
|
||||
service Attachments {
|
||||
option (require.auth) = AUTH_ONLY_AUTHENTICATED;
|
||||
|
||||
// Retrieve an upload form that can be used to perform a resumable upload
|
||||
rpc GetUploadForm(GetUploadFormRequest) returns (GetUploadFormResponse) {}
|
||||
}
|
||||
|
||||
message GetUploadFormRequest {}
|
||||
message GetUploadFormResponse {
|
||||
common.UploadForm upload_form = 1;
|
||||
}
|
||||
@@ -202,18 +202,18 @@ service BackupsAnonymous {
|
||||
message SignedPresentation {
|
||||
// Presentation of a BackupAuthCredential previously retrieved from
|
||||
// GetBackupAuthCredentials on the authenticated channel
|
||||
bytes presentation = 1;
|
||||
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;
|
||||
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;
|
||||
bytes public_key = 2 [(require.nonEmpty) = true];
|
||||
}
|
||||
|
||||
message SetPublicKeyResponse {
|
||||
@@ -367,23 +367,8 @@ message GetUploadFormRequest {
|
||||
}
|
||||
}
|
||||
message GetUploadFormResponse {
|
||||
message UploadForm {
|
||||
// Indicates the CDN type. 3 indicates resumable uploads using TUS
|
||||
uint32 cdn = 1;
|
||||
|
||||
// The location within the specified cdn where the finished upload can be found
|
||||
string key = 2;
|
||||
|
||||
// A map of headers to include with all upload requests. Potentially contains
|
||||
// time-limited upload credentials
|
||||
map<string, string> headers = 3;
|
||||
|
||||
// The URL to upload to with the appropriate protocol
|
||||
string signed_upload_location = 4;
|
||||
}
|
||||
|
||||
oneof outcome {
|
||||
UploadForm upload_form = 1;
|
||||
common.UploadForm upload_form = 1;
|
||||
|
||||
// The provided backup auth credential presentation could not be
|
||||
// authenticated. Either, the presentation could not be verified, or
|
||||
@@ -422,7 +407,7 @@ message CopyMediaRequest {
|
||||
SignedPresentation signed_presentation = 1;
|
||||
|
||||
// Items to copy
|
||||
repeated CopyMediaItem items = 2;
|
||||
repeated CopyMediaItem items = 2 [(require.size) = {min: 1, max: 1000}];
|
||||
}
|
||||
|
||||
message CopyMediaResponse {
|
||||
@@ -465,7 +450,7 @@ message ListMediaRequest {
|
||||
optional string cursor = 2;
|
||||
|
||||
// If provided, the maximum number of entries to return in a page
|
||||
uint32 limit = 3 [(require.range) = {min: 0, max: 10000}];
|
||||
uint32 limit = 3 [(require.range) = {min: 1, max: 10000}];
|
||||
}
|
||||
message ListMediaResponse {
|
||||
message ListEntry {
|
||||
@@ -529,13 +514,13 @@ message DeleteMediaItem {
|
||||
uint32 cdn = 1;
|
||||
|
||||
// The media_id of the object to delete
|
||||
bytes media_id = 2;
|
||||
bytes media_id = 2 [(require.exactlySize) = 15];
|
||||
}
|
||||
|
||||
message DeleteMediaRequest {
|
||||
SignedPresentation signed_presentation = 1;
|
||||
|
||||
repeated DeleteMediaItem items = 2;
|
||||
repeated DeleteMediaItem items = 2 [(require.size) = {min: 1, max: 1000}];
|
||||
}
|
||||
|
||||
message DeleteMediaResponse {
|
||||
|
||||
@@ -9,6 +9,8 @@ option java_multiple_files = true;
|
||||
|
||||
package org.signal.chat.common;
|
||||
|
||||
import "org/signal/chat/require.proto";
|
||||
|
||||
enum IdentityType {
|
||||
IDENTITY_TYPE_UNSPECIFIED = 0;
|
||||
IDENTITY_TYPE_ACI = 1;
|
||||
@@ -20,7 +22,7 @@ message ServiceIdentifier {
|
||||
IdentityType identity_type = 1;
|
||||
|
||||
// The UUID of the identity represented by this service identifier.
|
||||
bytes uuid = 2;
|
||||
bytes uuid = 2 [(require.exactlySize) = 16];
|
||||
}
|
||||
|
||||
message AccountIdentifiers {
|
||||
@@ -36,35 +38,40 @@ message AccountIdentifiers {
|
||||
}
|
||||
|
||||
message EcPreKey {
|
||||
// A locally-unique identifier for this key.
|
||||
uint64 key_id = 1;
|
||||
// A locally-unique identifier for this key, which will be provided by
|
||||
// peers using this key to encrypt messages so the private key can be looked
|
||||
// up.
|
||||
uint32 key_id = 1;
|
||||
|
||||
// The serialized form of the public key.
|
||||
bytes public_key = 2;
|
||||
// The public key, serialized in libsignal's elliptic-curve public key format.
|
||||
bytes public_key = 2 [(require.nonEmpty) = true];
|
||||
}
|
||||
|
||||
message EcSignedPreKey {
|
||||
// A locally-unique identifier for this key.
|
||||
uint64 key_id = 1;
|
||||
// A locally-unique identifier for this key, which will be provided by
|
||||
// peers using this key to encrypt messages so the private key can be looked
|
||||
// up.
|
||||
uint32 key_id = 1;
|
||||
|
||||
// The serialized form of the public key.
|
||||
bytes public_key = 2;
|
||||
// The public key, serialized in libsignal's elliptic-curve public key format.
|
||||
bytes public_key = 2 [(require.nonEmpty) = true];
|
||||
|
||||
// A signature of the public key, verifiable with the identity key for the
|
||||
// account/identity associated with this pre-key.
|
||||
bytes signature = 3;
|
||||
bytes signature = 3 [(require.nonEmpty) = true];
|
||||
}
|
||||
|
||||
message KemSignedPreKey {
|
||||
// A locally-unique identifier for this key.
|
||||
uint64 key_id = 1;
|
||||
// An locally-unique identifier for this key, which will be provided by peers
|
||||
// using this key to encrypt messages so the private key can be looked up.
|
||||
uint32 key_id = 1;
|
||||
|
||||
// The serialized form of the public key.
|
||||
bytes public_key = 2;
|
||||
// The public key, serialized in libsignal's Kyber1024 public key format.
|
||||
bytes public_key = 2 [(require.nonEmpty) = true];
|
||||
|
||||
// A signature of the public key, verifiable with the identity key for the
|
||||
// account/identity associated with this pre-key.
|
||||
bytes signature = 3;
|
||||
bytes signature = 3 [(require.nonEmpty) = true];
|
||||
}
|
||||
|
||||
enum DeviceCapability {
|
||||
@@ -87,6 +94,22 @@ message ZkCredential {
|
||||
/*
|
||||
* The ZK credential, using libsignal's serialization
|
||||
*/
|
||||
bytes credential = 2;
|
||||
bytes credential = 2 [(require.nonEmpty) = true];
|
||||
}
|
||||
|
||||
// An upload location and credentials which may be used to upload an object
|
||||
// to an external CDN
|
||||
message UploadForm {
|
||||
// Indicates the CDN type. 3 indicates resumable uploads using TUS
|
||||
uint32 cdn = 1;
|
||||
|
||||
// The location within the specified cdn where the finished upload can be found
|
||||
string key = 2;
|
||||
|
||||
// A map of headers to include with all upload requests. Potentially contains
|
||||
// time-limited upload credentials
|
||||
map<string, string> headers = 3;
|
||||
|
||||
// The URL to upload to with the appropriate protocol
|
||||
string signed_upload_location = 4;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ syntax = "proto3";
|
||||
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "org/signal/chat/require.proto";
|
||||
|
||||
package org.signal.chat.credentials;
|
||||
|
||||
// Provides methods for obtaining and verifying credentials for "external" services
|
||||
@@ -69,7 +71,7 @@ message CheckSvrCredentialsRequest {
|
||||
// A list of credentials from previously made calls to `ExternalServiceCredentials.GetExternalServiceCredentials()`
|
||||
// for `EXTERNAL_SERVICE_TYPE_SVR`. This list may contain credentials generated by different users. Up to 10 credentials
|
||||
// can be checked.
|
||||
repeated string passwords = 2;
|
||||
repeated string passwords = 2 [(require.nonEmpty) = true, (require.size) = {max: 10}];
|
||||
}
|
||||
|
||||
// For each of the credentials tokens in the `CheckSvrCredentialsRequest` contains the result of the check.
|
||||
|
||||
@@ -83,7 +83,7 @@ message RemoveDeviceRequest {
|
||||
message SetDeviceNameRequest {
|
||||
// A sequence of bytes that encodes an encrypted human-readable name for this
|
||||
// device.
|
||||
bytes name = 1 [(require.size) = {min: 1, max: 256}];
|
||||
bytes name = 1 [(require.size) = {min: 1, max: 225}];
|
||||
|
||||
// The identifier for the device for which to set a name.
|
||||
uint32 id = 2;
|
||||
|
||||
@@ -9,8 +9,12 @@ option java_multiple_files = true;
|
||||
|
||||
package org.signal.chat.keys;
|
||||
|
||||
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";
|
||||
|
||||
// Provides methods for working with pre-keys.
|
||||
service Keys {
|
||||
@@ -111,6 +115,9 @@ message GetPreKeysAnonymousRequest {
|
||||
|
||||
// A group send endorsement token for the targeted account.
|
||||
bytes group_send_token = 3;
|
||||
|
||||
// The destination account allows unrestricted unidentified access
|
||||
google.protobuf.Empty unrestricted_access = 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +153,7 @@ message GetPreKeysResponse {
|
||||
|
||||
// Either the target account was not found, no active device with the given
|
||||
// ID (if specified) was found on the target account.
|
||||
errors.NotFound target_not_found = 2;
|
||||
errors.NotFound target_not_found = 2 [(tag.reason) = "not_found"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,10 +164,10 @@ message GetPreKeysAnonymousResponse {
|
||||
|
||||
// Either the target account was not found, no active device with the given
|
||||
// ID (if specified) was found on the target account.
|
||||
errors.NotFound target_not_found = 2;
|
||||
errors.NotFound target_not_found = 2 [(tag.reason) = "not_found"];
|
||||
|
||||
// The provided unidentified authorization credential was invalid
|
||||
errors.FailedUnidentifiedAuthorization failed_unidentified_authorization = 3;
|
||||
errors.FailedUnidentifiedAuthorization failed_unidentified_authorization = 3 [(tag.reason) = "failed_unidentified_authorization"];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +177,7 @@ message SetOneTimeEcPreKeysRequest {
|
||||
common.IdentityType identity_type = 1;
|
||||
|
||||
// The unsigned EC pre-keys to be stored.
|
||||
repeated common.EcPreKey pre_keys = 2;
|
||||
repeated common.EcPreKey pre_keys = 2 [(require.size) = {min: 1, max: 100}];
|
||||
}
|
||||
|
||||
message SetOneTimeKemSignedPreKeysRequest {
|
||||
@@ -179,7 +186,7 @@ message SetOneTimeKemSignedPreKeysRequest {
|
||||
common.IdentityType identity_type = 1;
|
||||
|
||||
// The KEM pre-keys to be stored.
|
||||
repeated common.KemSignedPreKey pre_keys = 2;
|
||||
repeated common.KemSignedPreKey pre_keys = 2 [(require.size) = {min: 1, max: 100}];
|
||||
}
|
||||
|
||||
message SetEcSignedPreKeyRequest {
|
||||
@@ -187,7 +194,7 @@ message SetEcSignedPreKeyRequest {
|
||||
common.IdentityType identity_type = 1;
|
||||
|
||||
// The signed EC pre-key itself.
|
||||
common.EcSignedPreKey signed_pre_key = 2;
|
||||
common.EcSignedPreKey signed_pre_key = 2 [(require.present) = true];
|
||||
}
|
||||
|
||||
message SetKemLastResortPreKeyRequest {
|
||||
@@ -195,7 +202,7 @@ message SetKemLastResortPreKeyRequest {
|
||||
common.IdentityType identity_type = 1;
|
||||
|
||||
// The signed KEM pre-key itself.
|
||||
common.KemSignedPreKey signed_pre_key = 2;
|
||||
common.KemSignedPreKey signed_pre_key = 2 [(require.present) = true];
|
||||
}
|
||||
|
||||
message SetPreKeyResponse {
|
||||
@@ -205,7 +212,7 @@ message CheckIdentityKeyRequest {
|
||||
// The service identifier of the account for which we want to check the associated identity key fingerprint.
|
||||
common.ServiceIdentifier target_identifier = 1;
|
||||
// The most significant 4 bytes of the SHA-256 hash of the identity key associated with the target account/identity type.
|
||||
bytes fingerprint = 2;
|
||||
bytes fingerprint = 2 [(require.exactlySize) = 4];
|
||||
}
|
||||
|
||||
message CheckIdentityKeyResponse {
|
||||
|
||||
@@ -70,6 +70,10 @@ message IndividualRecipientMessageBundle {
|
||||
|
||||
// The content of the message to deliver to the destination device.
|
||||
bytes payload = 2 [(require.size) = {min: 1, max: 262144}]; // 256 KiB
|
||||
|
||||
// The message type of the message. If this message is part of an
|
||||
// unidentified send, this must be UNIDENTIFIED_SENDER
|
||||
SendMessageType type = 3;
|
||||
}
|
||||
|
||||
// The time, in milliseconds since the epoch, at which this message was
|
||||
@@ -87,7 +91,7 @@ message IndividualRecipientMessageBundle {
|
||||
map<uint32, Message> messages = 2 [(require.nonEmpty) = true];
|
||||
}
|
||||
|
||||
enum AuthenticatedSenderMessageType {
|
||||
enum SendMessageType {
|
||||
UNSPECIFIED = 0;
|
||||
|
||||
// A double-ratchet message represents a "normal," "unsealed-sender" message
|
||||
@@ -102,7 +106,7 @@ enum AuthenticatedSenderMessageType {
|
||||
|
||||
// A plaintext message is used solely to convey encryption error receipts
|
||||
// and never contains encrypted message content. Encryption error receipts
|
||||
// must be delivered in plaintext because, encryption/decryption of a prior
|
||||
// must be delivered in plaintext because encryption/decryption of a prior
|
||||
// message failed and there is no reason to believe that
|
||||
// encryption/decryption of subsequent messages with the same key material
|
||||
// would succeed.
|
||||
@@ -110,6 +114,14 @@ enum AuthenticatedSenderMessageType {
|
||||
// Critically, plaintext messages never have "real" message content
|
||||
// generated by users. Plaintext messages include sender information.
|
||||
PLAINTEXT_CONTENT = 3;
|
||||
|
||||
// An unidentified sender message is an encrypted message. No other
|
||||
// information about the type of the encrypted message is known to the server.
|
||||
//
|
||||
// Unidenitfied sender messages require an unidentified access token or a
|
||||
// group send endorsement token to prove the unidentified sender is authorized
|
||||
// to send messages to the destination.
|
||||
UNIDENTIFIED_SENDER = 4;
|
||||
}
|
||||
|
||||
message SendAuthenticatedSenderMessageRequest {
|
||||
@@ -117,20 +129,17 @@ message SendAuthenticatedSenderMessageRequest {
|
||||
// The service identifier of the account to which to deliver the message.
|
||||
common.ServiceIdentifier destination = 1;
|
||||
|
||||
// The type identifier for this message.
|
||||
AuthenticatedSenderMessageType type = 2 [(require.specified) = true];
|
||||
|
||||
// If true, this message will only be delivered to destination devices that
|
||||
// have an active message delivery channel with a Signal server.
|
||||
bool ephemeral = 3;
|
||||
bool ephemeral = 2;
|
||||
|
||||
// Indicates whether this message is urgent and should trigger a high-priority
|
||||
// notification if the destination device does not have an active message
|
||||
// delivery channel with a Signal server
|
||||
bool urgent = 4;
|
||||
bool urgent = 3;
|
||||
|
||||
// The messages to send to the destination account.
|
||||
IndividualRecipientMessageBundle messages = 5;
|
||||
IndividualRecipientMessageBundle messages = 4;
|
||||
}
|
||||
|
||||
message SendMessageAuthenticatedSenderResponse {
|
||||
@@ -159,16 +168,13 @@ message SendMessageAuthenticatedSenderResponse {
|
||||
|
||||
message SendSyncMessageRequest {
|
||||
|
||||
// The type identifier for this message.
|
||||
AuthenticatedSenderMessageType type = 1 [(require.specified) = true];
|
||||
|
||||
// Indicates whether this message is urgent and should trigger a high-priority
|
||||
// notification if the destination device does not have an active message
|
||||
// delivery channel with a Signal server
|
||||
bool urgent = 2;
|
||||
bool urgent = 1;
|
||||
|
||||
// The messages to send to the destination account.
|
||||
IndividualRecipientMessageBundle messages = 3;
|
||||
IndividualRecipientMessageBundle messages = 2;
|
||||
}
|
||||
|
||||
message SendSealedSenderMessageRequest {
|
||||
@@ -192,10 +198,13 @@ message SendSealedSenderMessageRequest {
|
||||
oneof authorization {
|
||||
|
||||
// The unidentified access key (UAK) for the destination account.
|
||||
bytes unidentified_access_key = 5;
|
||||
bytes unidentified_access_key = 5 [(require.exactlySize) = 16];
|
||||
|
||||
// A group send endorsement token for the destination account.
|
||||
bytes group_send_token = 6;
|
||||
|
||||
// The destination account allows unrestricted unidentified access
|
||||
google.protobuf.Empty unrestricted_access = 7;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +361,7 @@ message ChallengeRequired {
|
||||
|
||||
// An opaque token identifying this challenge request. Clients must generally
|
||||
// submit this token when submitting a challenge response.
|
||||
bytes token = 1;
|
||||
string token = 1;
|
||||
|
||||
// A list of challenge types callers may choose to complete to resolve the
|
||||
// challenge requirement. May be empty, in which case callers cannot resolve
|
||||
|
||||
@@ -14,8 +14,9 @@ import "google/protobuf/descriptor.proto";
|
||||
extend google.protobuf.FieldOptions {
|
||||
/*
|
||||
* Requires a field to have content of non-zero size/length.
|
||||
* Applies to both `optional` and regular fields, i.e. if the field is not set or has a default value,
|
||||
* it's considered to be empty.
|
||||
* Applies to both `optional` and regular fields, i.e. if the field is not set
|
||||
* or has a default value, it's considered to be empty. This does not apply
|
||||
* to fields that are contained in a `oneof`.
|
||||
*
|
||||
* ```
|
||||
* import "org/signal/chat/require.proto";
|
||||
@@ -57,8 +58,10 @@ extend google.protobuf.FieldOptions {
|
||||
|
||||
/*
|
||||
* Requires a size/length of a field to be within certain boundaries.
|
||||
* Applies to both `optional` and regular fields, i.e. if the field is not set or has a default value,
|
||||
* its size considered to be zero.
|
||||
* Applies to both `optional` and regular fields, i.e. if the field is not set
|
||||
* or has a default value, its size considered to be zero. However, if the
|
||||
* field is contained in a `oneof` and is not set, this annotation does not
|
||||
* apply.
|
||||
*
|
||||
* ```
|
||||
* import "org/signal/chat/require.proto";
|
||||
@@ -76,9 +79,11 @@ extend google.protobuf.FieldOptions {
|
||||
optional SizeConstraint size = 70003;
|
||||
|
||||
/*
|
||||
* Requires a size/length of a field to be one of the specified values.
|
||||
* Applies to both `optional` and regular fields, i.e. if the field is not set or has a default value,
|
||||
* its size considered to be zero.
|
||||
* Requires a size/length of a field to be within certain boundaries.
|
||||
* Applies to both `optional` and regular fields, i.e. if the field is not set
|
||||
* or has a default value, its size considered to be zero. However, if the
|
||||
* field is contained in a `oneof` and is not set, this annotation does not
|
||||
* apply.
|
||||
*
|
||||
* ```
|
||||
* import "org/signal/chat/require.proto";
|
||||
@@ -130,7 +135,8 @@ extend google.protobuf.FieldOptions {
|
||||
* Require a value of a message field to be present.
|
||||
*
|
||||
* Applies to both `optional` and regular fields (both of which have explicit
|
||||
* presence for the message type anyways)
|
||||
* presence for the message type anyways). This does not apply to fields that
|
||||
* are contained in a `oneof`.
|
||||
*
|
||||
* ```
|
||||
* import "org/signal/chat/require.proto";
|
||||
|
||||
@@ -13,10 +13,12 @@ pub mod proto {
|
||||
pub mod errors {
|
||||
tonic::include_proto!("org.signal.chat.errors");
|
||||
}
|
||||
|
||||
pub mod account {
|
||||
tonic::include_proto!("org.signal.chat.account");
|
||||
}
|
||||
pub mod attachments {
|
||||
tonic::include_proto!("org.signal.chat.attachments");
|
||||
}
|
||||
pub mod backup {
|
||||
tonic::include_proto!("org.signal.chat.backup");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user