diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 040f12aba..123c5db87 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,2 +1,4 @@ v0.90.1 +- Support gRPC for getUploadForm() + diff --git a/node/ts/Native.ts b/node/ts/Native.ts index 947727566..41063c346 100644 --- a/node/ts/Native.ts +++ b/node/ts/Native.ts @@ -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; } diff --git a/rust/bridge/shared/types/src/net/remote_config.rs b/rust/bridge/shared/types/src/net/remote_config.rs index 9b7ee4c5b..981f8b8cb 100644 --- a/rust/bridge/shared/types/src/net/remote_config.rs +++ b/rust/bridge/shared/types/src/net/remote_config.rs @@ -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(); diff --git a/rust/net/chat/src/grpc/backups.rs b/rust/net/chat/src/grpc/backups.rs index 35ae47490..c46e53a84 100644 --- a/rust/net/chat/src/grpc/backups.rs +++ b/rust/net/chat/src/grpc/backups.rs @@ -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 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([ diff --git a/rust/net/chat/src/grpc/messages.rs b/rust/net/chat/src/grpc/messages.rs index cf2e93077..264924afc 100644 --- a/rust/net/chat/src/grpc/messages.rs +++ b/rust/net/chat/src/grpc/messages.rs @@ -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 { } } +impl std::fmt::Display for Redact { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let Self(attachments::GetUploadFormRequest {}) = self; + f.debug_struct("attachments::GetUploadFormRequest").finish() + } +} + +#[async_trait] +impl crate::api::messages::AuthenticatedChatApi for Auth { + async fn send_message( + &self, + _destination: ServiceId, + _timestamp: Timestamp, + _contents: &[SingleOutboundUnsealedMessage<'_>], + _online_only: bool, + _urgent: bool, + ) -> Result<(), RequestError> { + unimplemented!() + } + + async fn send_sync_message( + &self, + _timestamp: Timestamp, + _contents: &[SingleOutboundUnsealedMessage<'_>], + _urgent: bool, + ) -> Result<(), RequestError> { + unimplemented!() + } + + async fn get_upload_form(&self) -> Result> { + 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(), + } + ); + } } diff --git a/rust/net/chat/src/ws/messages.rs b/rust/net/chat/src/ws/messages.rs index a2c924f64..26e2df96e 100644 --- a/rust/net/chat/src/ws/messages.rs +++ b/rust/net/chat/src/ws/messages.rs @@ -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 crate::api::messages::UnauthenticatedChatApi for Unauth { +impl UnauthenticatedChatApi for Unauth { async fn send_message( &self, destination: ServiceId, @@ -398,6 +399,11 @@ impl crate::api::messages::AuthenticatedChatApi for Aut } async fn get_upload_form(&self) -> Result> { + 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, }; diff --git a/rust/net/grpc/build.rs b/rust/net/grpc/build.rs index dc74304c1..31ee66501 100644 --- a/rust/net/grpc/build.rs +++ b/rust/net/grpc/build.rs @@ -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", diff --git a/rust/net/grpc/proto/org/signal/chat/account.proto b/rust/net/grpc/proto/org/signal/chat/account.proto index 304c1411d..cd77c274e 100644 --- a/rust/net/grpc/proto/org/signal/chat/account.proto +++ b/rust/net/grpc/proto/org/signal/chat/account.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 { diff --git a/rust/net/grpc/proto/org/signal/chat/attachments.proto b/rust/net/grpc/proto/org/signal/chat/attachments.proto new file mode 100644 index 000000000..9634407dc --- /dev/null +++ b/rust/net/grpc/proto/org/signal/chat/attachments.proto @@ -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; +} diff --git a/rust/net/grpc/proto/org/signal/chat/backups.proto b/rust/net/grpc/proto/org/signal/chat/backups.proto index 1dac97005..e06ae7645 100644 --- a/rust/net/grpc/proto/org/signal/chat/backups.proto +++ b/rust/net/grpc/proto/org/signal/chat/backups.proto @@ -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 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 { diff --git a/rust/net/grpc/proto/org/signal/chat/common.proto b/rust/net/grpc/proto/org/signal/chat/common.proto index f196af185..21981c49a 100644 --- a/rust/net/grpc/proto/org/signal/chat/common.proto +++ b/rust/net/grpc/proto/org/signal/chat/common.proto @@ -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 headers = 3; + + // The URL to upload to with the appropriate protocol + string signed_upload_location = 4; +} diff --git a/rust/net/grpc/proto/org/signal/chat/credentials.proto b/rust/net/grpc/proto/org/signal/chat/credentials.proto index 596e2a48e..59ccb0cb0 100644 --- a/rust/net/grpc/proto/org/signal/chat/credentials.proto +++ b/rust/net/grpc/proto/org/signal/chat/credentials.proto @@ -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. diff --git a/rust/net/grpc/proto/org/signal/chat/device.proto b/rust/net/grpc/proto/org/signal/chat/device.proto index 977231dde..90eeb7ca1 100644 --- a/rust/net/grpc/proto/org/signal/chat/device.proto +++ b/rust/net/grpc/proto/org/signal/chat/device.proto @@ -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; diff --git a/rust/net/grpc/proto/org/signal/chat/keys.proto b/rust/net/grpc/proto/org/signal/chat/keys.proto index eb3a81164..cbc2b9bd9 100644 --- a/rust/net/grpc/proto/org/signal/chat/keys.proto +++ b/rust/net/grpc/proto/org/signal/chat/keys.proto @@ -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 { diff --git a/rust/net/grpc/proto/org/signal/chat/messages.proto b/rust/net/grpc/proto/org/signal/chat/messages.proto index 6b6790c44..7d3d2c1d4 100644 --- a/rust/net/grpc/proto/org/signal/chat/messages.proto +++ b/rust/net/grpc/proto/org/signal/chat/messages.proto @@ -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 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 diff --git a/rust/net/grpc/proto/org/signal/chat/require.proto b/rust/net/grpc/proto/org/signal/chat/require.proto index abc870a1c..071c12a2a 100644 --- a/rust/net/grpc/proto/org/signal/chat/require.proto +++ b/rust/net/grpc/proto/org/signal/chat/require.proto @@ -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"; diff --git a/rust/net/grpc/src/lib.rs b/rust/net/grpc/src/lib.rs index 6a5876f3c..83b6a1e63 100644 --- a/rust/net/grpc/src/lib.rs +++ b/rust/net/grpc/src/lib.rs @@ -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"); }