send push notification info with contact code

This commit is contained in:
Andrea Maria Piana 2020-07-31 15:46:27 +02:00
parent 541756c777
commit 5544e710ab
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
3 changed files with 148 additions and 44 deletions

View File

@ -305,10 +305,29 @@ func (m *Messenger) Start() error {
// handleSendContactCode sends a public message wrapped in the encryption
// layer, which will propagate our bundle
func (m *Messenger) handleSendContactCode() error {
var payload []byte
if m.pushNotificationClient != nil && m.pushNotificationClient.Enabled() {
info, err := m.pushNotificationClient.MyPushNotificationQueryInfo()
if err != nil {
return err
}
if len(info) != 0 {
contactCode := &protobuf.ContactCodeAdvertisement{
PushNotificationInfo: info,
}
payload, err = proto.Marshal(contactCode)
if err != nil {
return err
}
}
}
contactCodeTopic := transport.ContactCodeTopic(&m.identity.PublicKey)
rawMessage := common.RawMessage{
LocalChatID: contactCodeTopic,
Payload: nil,
Payload: payload,
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
@ -2027,6 +2046,18 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
}
// We continue in any case, no changes to messenger
continue
case protobuf.ContactCodeAdvertisement:
logger.Debug("Received ContactCodeAdvertisement")
if m.pushNotificationClient == nil {
continue
}
logger.Debug("Handling PushNotificationRegistrationResponse")
if err := m.pushNotificationClient.HandleContactCodeAdvertisement(publicKey, msg.ParsedMessage.Interface().(protobuf.ContactCodeAdvertisement)); err != nil {
logger.Warn("failed to handle ContactCodeAdvertisement", zap.Error(err))
}
// We continue in any case, no changes to messenger
continue
case protobuf.PushNotificationResponse:
logger.Debug("Received PushNotificationResponse")
if m.pushNotificationClient == nil {

View File

@ -318,6 +318,53 @@ func (c *Client) HandlePushNotificationRegistrationResponse(publicKey *ecdsa.Pub
return c.persistence.UpsertServer(server)
}
// processQueryInfo takes info about push notifications and validates them
func (c *Client) processQueryInfo(clientPublicKey *ecdsa.PublicKey, serverPublicKey *ecdsa.PublicKey, info *protobuf.PushNotificationQueryInfo) error {
// make sure the public key matches
if !bytes.Equal(info.PublicKey, common.HashPublicKey(clientPublicKey)) {
c.config.Logger.Warn("reply for different key, ignoring")
return errors.New("reply for a different key, ignoreing")
}
accessToken := info.AccessToken
// the user wants notification from contacts only, try to decrypt the access token to see if we are in their contacts
if len(accessToken) == 0 && len(info.AllowedKeyList) != 0 {
accessToken = c.handleAllowedKeyList(clientPublicKey, info.AllowedKeyList)
}
// no luck
if len(accessToken) == 0 {
c.config.Logger.Debug("not in the allowed key list")
return nil
}
// We check the user has allowed this server to store this particular
// access token, otherwise anyone could reply with a fake token
// and receive notifications for a user
if err := c.handleGrant(clientPublicKey, serverPublicKey, info.Grant, accessToken); err != nil {
c.config.Logger.Warn("grant verification failed, ignoring", zap.Error(err))
return err
}
pushNotificationInfo := &PushNotificationInfo{
PublicKey: clientPublicKey,
ServerPublicKey: serverPublicKey,
AccessToken: accessToken,
InstallationID: info.InstallationId,
Version: info.Version,
RetrievedAt: time.Now().Unix(),
}
err := c.persistence.SavePushNotificationInfo([]*PushNotificationInfo{pushNotificationInfo})
if err != nil {
c.config.Logger.Error("failed to save push notifications", zap.Error(err))
return err
}
return nil
}
// HandlePushNotificationQueryResponse should update the data in the database for a given user
func (c *Client) HandlePushNotificationQueryResponse(serverPublicKey *ecdsa.PublicKey, response protobuf.PushNotificationQueryResponse) error {
c.config.Logger.Debug("received push notification query response", zap.Any("response", response))
@ -326,61 +373,42 @@ func (c *Client) HandlePushNotificationQueryResponse(serverPublicKey *ecdsa.Publ
}
// get the public key associated with this query
publicKey, err := c.persistence.GetQueryPublicKey(response.MessageId)
clientPublicKey, err := c.persistence.GetQueryPublicKey(response.MessageId)
if err != nil {
return err
}
if publicKey == nil {
if clientPublicKey == nil {
c.config.Logger.Debug("query not found")
return nil
}
var pushNotificationInfo []*PushNotificationInfo
// process query, make sure to validate grant as coming from the server
for _, info := range response.Info {
// make sure the public key matches
if !bytes.Equal(info.PublicKey, common.HashPublicKey(publicKey)) {
c.config.Logger.Warn("reply for different key, ignoring")
continue
}
accessToken := info.AccessToken
// the user wants notification from contacts only, try to decrypt the access token to see if we are in their contacts
if len(accessToken) == 0 && len(info.AllowedKeyList) != 0 {
accessToken = c.handleAllowedKeyList(publicKey, info.AllowedKeyList)
}
// no luck
if len(accessToken) == 0 {
c.config.Logger.Debug("not in the allowed key list")
continue
}
// We check the user has allowed this server to store this particular
// access token, otherwise anyone could reply with a fake token
// and receive notifications for a user
if err := c.handleGrant(publicKey, serverPublicKey, info.Grant, accessToken); err != nil {
c.config.Logger.Warn("grant verification failed, ignoring", zap.Error(err))
continue
}
pushNotificationInfo = append(pushNotificationInfo, &PushNotificationInfo{
PublicKey: publicKey,
ServerPublicKey: serverPublicKey,
AccessToken: accessToken,
InstallationID: info.InstallationId,
Version: info.Version,
RetrievedAt: time.Now().Unix(),
})
}
err = c.persistence.SavePushNotificationInfo(pushNotificationInfo)
err := c.processQueryInfo(clientPublicKey, serverPublicKey, info)
if err != nil {
c.config.Logger.Warn("failed to process info", zap.Any("info", info), zap.Error(err))
continue
}
}
return nil
}
// HandleContactCodeAdvertisement checks if there are any info and process them
func (c *Client) HandleContactCodeAdvertisement(clientPublicKey *ecdsa.PublicKey, message protobuf.ContactCodeAdvertisement) error {
c.config.Logger.Debug("received contact code advertisement", zap.Any("advertisement", message))
for _, info := range message.PushNotificationInfo {
c.config.Logger.Debug("handling push notification query info")
serverPublicKey, err := crypto.UnmarshalPubkey(info.ServerPublicKey)
if err != nil {
c.config.Logger.Error("failed to save push notifications", zap.Error(err))
return err
}
err = c.processQueryInfo(clientPublicKey, serverPublicKey, info)
if err != nil {
return err
}
}
return nil
}
@ -443,6 +471,10 @@ func (c *Client) GetPushNotificationInfo(publicKey *ecdsa.PublicKey, installatio
return c.persistence.GetPushNotificationInfo(publicKey, installationIDs)
}
func (c *Client) Enabled() bool {
return c.config.RemoteNotificationsEnabled
}
func (c *Client) EnableSending() {
c.config.SendEnabled = true
}
@ -1275,6 +1307,48 @@ func (c *Client) handleAllowedKeyList(publicKey *ecdsa.PublicKey, allowedKeyList
return ""
}
func (c *Client) MyPushNotificationQueryInfo() ([]*protobuf.PushNotificationQueryInfo, error) {
// Nothing to do
if c.lastPushNotificationRegistration == nil || c.lastPushNotificationRegistration.Unregister {
return nil, nil
}
var response []*protobuf.PushNotificationQueryInfo
servers, err := c.persistence.GetServers()
if err != nil {
return nil, err
}
for _, server := range servers {
// ignore non-registered servers
if !server.Registered {
continue
}
// build grant for this specific server
grant, err := c.buildGrantSignature(server.PublicKey, c.lastPushNotificationRegistration.AccessToken)
if err != nil {
c.config.Logger.Error("failed to build grant", zap.Error(err))
return nil, err
}
queryInfo := &protobuf.PushNotificationQueryInfo{
InstallationId: c.config.InstallationID,
// is this the right key?
PublicKey: common.HashPublicKey(&c.config.Identity.PublicKey),
Version: c.lastPushNotificationRegistration.Version,
Grant: grant,
ServerPublicKey: crypto.CompressPubkey(server.PublicKey),
}
if c.lastPushNotificationRegistration.AllowFromContactsOnly {
queryInfo.AllowedKeyList = c.lastPushNotificationRegistration.AllowedKeyList
} else {
queryInfo.AccessToken = c.lastPushNotificationRegistration.AccessToken
}
response = append(response, queryInfo)
}
return response, nil
}
// queryPushNotificationInfo sends a message to any server who has the given user registered.
// it uses an ephemeral key so the identity of the client querying is not disclosed
func (c *Client) queryPushNotificationInfo(publicKey *ecdsa.PublicKey) error {

View File

@ -226,7 +226,6 @@ func (m *StatusMessage) HandleApplication() error {
case protobuf.ApplicationMetadataMessage_PAIR_INSTALLATION:
return m.unmarshalProtobufData(new(protobuf.PairInstallation))
case protobuf.ApplicationMetadataMessage_CONTACT_CODE_ADVERTISEMENT:
return m.unmarshalProtobufData(new(protobuf.ContactCodeAdvertisement))
case protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_REQUEST: