diff --git a/protocol/push_notification_server/push_notification_server.go b/protocol/push_notification_server/push_notification_server.go index f9971a70b..9c136b400 100644 --- a/protocol/push_notification_server/push_notification_server.go +++ b/protocol/push_notification_server/push_notification_server.go @@ -3,11 +3,14 @@ package protocol import ( "errors" "fmt" + "io" "crypto/aes" "crypto/cipher" "crypto/ecdsa" + "github.com/golang/protobuf/proto" + "github.com/status-im/status-go/eth-node/crypto/ecies" "github.com/status-im/status-go/protocol/protobuf" ) @@ -15,9 +18,11 @@ import ( const encryptedPayloadKeyLength = 16 const nonceLength = 12 -var ErrInvalidPushNotificationRegisterVersion = errors.New("invalid version") -var ErrEmptyPushNotificationRegisterPayload = errors.New("empty payload") -var ErrEmptyPushNotificationRegisterPublicKey = errors.New("no public key") +var ErrInvalidPushNotificationPreferencesVersion = errors.New("invalid version") +var ErrEmptyPushNotificationPreferencesPayload = errors.New("empty payload") +var ErrEmptyPushNotificationPreferencesPublicKey = errors.New("no public key") +var ErrCouldNotUnmarshalPushNotificationPreferences = errors.New("could not unmarshal preferences") +var ErrInvalidCiphertextLength = errors.New("invalid cyphertext length") type Config struct { // Identity is our identity key @@ -35,20 +40,24 @@ func New(persistence *Persistence) *Server { return &Server{persistence: persistence} } -func (p *Server) ValidateRegistration(previousPreferences *protobuf.PushNotificationPreferences, publicKey *ecdsa.PublicKey, payload []byte) error { - if payload == nil { - return ErrEmptyPushNotificationRegisterPayload - } - - if publicKey == nil { - return ErrEmptyPushNotificationRegisterPublicKey - } - - sharedKey, err := ecies.ImportECDSA(p.config.Identity).GenerateShared( +func (p *Server) generateSharedKey(publicKey *ecdsa.PublicKey) ([]byte, error) { + return ecies.ImportECDSA(p.config.Identity).GenerateShared( ecies.ImportECDSAPublic(publicKey), encryptedPayloadKeyLength, encryptedPayloadKeyLength, ) +} + +func (p *Server) ValidateRegistration(previousPreferences *protobuf.PushNotificationPreferences, publicKey *ecdsa.PublicKey, payload []byte) error { + if payload == nil { + return ErrEmptyPushNotificationPreferencesPayload + } + + if publicKey == nil { + return ErrEmptyPushNotificationPreferencesPublicKey + } + + sharedKey, err := p.generateSharedKey(publicKey) if err != nil { return err } @@ -58,17 +67,26 @@ func (p *Server) ValidateRegistration(previousPreferences *protobuf.PushNotifica return err } + preferences := &protobuf.PushNotificationPreferences{} + + if err := proto.Unmarshal(decryptedPayload, preferences); err != nil { + return ErrCouldNotUnmarshalPushNotificationPreferences + } + + if preferences.Version < 1 { + return ErrInvalidPushNotificationPreferencesVersion + } fmt.Println(decryptedPayload) /*if newRegistration.Version < 1 { - return ErrInvalidPushNotificationRegisterVersion + return ErrInvalidPushNotificationPreferencesVersion }*/ return nil } func decrypt(cyphertext []byte, key []byte) ([]byte, error) { if len(cyphertext) < nonceLength { - return nil, errors.New("invalid cyphertext length") + return nil, ErrInvalidCiphertextLength } c, err := aes.NewCipher(key) @@ -82,5 +100,24 @@ func decrypt(cyphertext []byte, key []byte) ([]byte, error) { } nonce := cyphertext[:nonceLength] - return gcm.Open(nil, nonce, cyphertext, nil) + return gcm.Open(nil, nonce, cyphertext[nonceLength:], nil) +} + +func encrypt(plaintext []byte, key []byte, reader io.Reader) ([]byte, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(c) + if err != nil { + return nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + if _, err = io.ReadFull(reader, nonce); err != nil { + return nil, err + } + + return gcm.Seal(nonce, nonce, plaintext, nil), nil } diff --git a/protocol/push_notification_server/push_notification_server_test.go b/protocol/push_notification_server/push_notification_server_test.go index 5307da5c7..9fcadd47d 100644 --- a/protocol/push_notification_server/push_notification_server_test.go +++ b/protocol/push_notification_server/push_notification_server_test.go @@ -1,40 +1,62 @@ package protocol import ( + "crypto/rand" "testing" "github.com/ethereum/go-ethereum/crypto" + "github.com/golang/protobuf/proto" "github.com/stretchr/testify/require" - //nodecrypto "github.com/status-im/status-go/eth-node/crypto" - //"github.com/status-im/status-go/protocol/protobuf" + + "github.com/status-im/status-go/protocol/protobuf" ) func TestPushNotificationServerValidateRegistration(t *testing.T) { identity, err := crypto.GenerateKey() require.NoError(t, err) - key, err := crypto.GenerateKey() - require.NoError(t, err) - config := &Config{ Identity: identity, } server := Server{config: config} + key, err := crypto.GenerateKey() + require.NoError(t, err) + + sharedKey, err := server.generateSharedKey(&key.PublicKey) + require.NoError(t, err) + // Empty payload - require.Equal(t, ErrEmptyPushNotificationRegisterPayload, server.ValidateRegistration(nil, &key.PublicKey, nil)) + require.Equal(t, ErrEmptyPushNotificationPreferencesPayload, server.ValidateRegistration(nil, &key.PublicKey, nil)) // Empty key - require.Equal(t, ErrEmptyPushNotificationRegisterPublicKey, server.ValidateRegistration(nil, nil, []byte("payload"))) + require.Equal(t, ErrEmptyPushNotificationPreferencesPublicKey, server.ValidateRegistration(nil, nil, []byte("payload"))) - /* - // Invalid signature - signature, err := nodecrypto.SignBytes([]byte("a"), key) + // Invalid cyphertext length + require.Equal(t, ErrInvalidCiphertextLength, server.ValidateRegistration(nil, &key.PublicKey, []byte("too short"))) - require.Equal(t, ErrInvalidPushNotificationRegisterVersion, server.ValidateRegistration(nil, &protobuf.PushNotificationRegister{ - Payload: []byte("btahtasht"), - Signature: signature, - }))*/ + // Invalid cyphertext length + require.Equal(t, ErrInvalidCiphertextLength, server.ValidateRegistration(nil, &key.PublicKey, []byte("too short"))) + // Invalid ciphertext + require.Error(t, ErrInvalidCiphertextLength, server.ValidateRegistration(nil, &key.PublicKey, []byte("not too short but invalid"))) + + // Different key ciphertext + cyphertext, err := encrypt([]byte("plaintext"), make([]byte, 32), rand.Reader) + require.NoError(t, err) + require.Error(t, server.ValidateRegistration(nil, &key.PublicKey, cyphertext)) + + // Right cyphertext but non unmarshable payload + cyphertext, err = encrypt([]byte("plaintext"), sharedKey, rand.Reader) + require.NoError(t, err) + require.Equal(t, ErrCouldNotUnmarshalPushNotificationPreferences, server.ValidateRegistration(nil, &key.PublicKey, cyphertext)) + + // Version set to 0 + payload, err := proto.Marshal(&protobuf.PushNotificationPreferences{}) + require.NoError(t, err) + + cyphertext, err = encrypt(payload, sharedKey, rand.Reader) + require.NoError(t, err) + require.Equal(t, ErrInvalidPushNotificationPreferencesVersion, server.ValidateRegistration(nil, &key.PublicKey, cyphertext)) }