Skip to content

Commit

Permalink
Reintroduce encryption for Android notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
crysxd committed Dec 3, 2023
1 parent 541137b commit 02320ce
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 6 deletions.
13 changes: 12 additions & 1 deletion moonraker_octoapp/moonrakerappstorage.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from octoapp.sentry import Sentry
from .moonrakerdatabase import MoonrakerDatabase
from octoapp.appsstorage import AppInstance, AppStorageHelper
import uuid
from Crypto import Random
from Crypto.Cipher import AES

class MoonrakerAppStorage:

Expand All @@ -17,10 +20,18 @@ def GetAllApps(self) -> [AppInstance]:
apps = self.Database.GetAppsEntry()
return list(map(lambda app: AppInstance.FromDict(app), apps))


# !! Platform Command Handler Interface Function !!
#
# This must receive a lsit of AppInstnace
#
def RemoveApps(self, apps:[AppInstance]):
apps = list(map(lambda app: app.FcmToken, apps))
self.Database.RemoveAppEntries(apps)
self.Database.RemoveAppEntries(apps)

# !! Platform Command Handler Interface Function !!
#
# This must receive a lsit of AppInstnace
#
def GetOrCreateEncryptionKey(self):
return self.Database.GetOrCreateEncryptionKey()
40 changes: 37 additions & 3 deletions moonraker_octoapp/moonrakerdatabase.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import time
import threading
import uuid

from octoapp.sentry import Sentry
from octoapp.appsstorage import AppInstance
Expand All @@ -14,6 +15,7 @@ def __init__(self, printerId:str, pluginVersion:str) -> None:
self.PrinterId = printerId
self.PluginVersion = pluginVersion
self.PresenceAnnouncementRunning = False
self.CachedEncryptionKey = None
self._continuouslyAnnouncePresence()


Expand Down Expand Up @@ -61,6 +63,35 @@ def GetPrinterName(self) -> str:
Sentry.Error("Database", "Failed to load Fluidd printer name"+fluiddResult.GetLoggingErrorStr())

return "Klipper"


def GetOrCreateEncryptionKey(self):
if self.CachedEncryptionKey is None:
result = MoonrakerClient.Get().SendJsonRpcRequest("server.database.get_item",
{
"namespace": "ocotapp",
"key": "public.encryptionKey",
})
if result.HasError() is False and result.GetResult() is not None:
self.CachedEncryptionKey = result.GetResult()["value"]
else:
raise Exception("Failed to get encryption key %s" % result.GetErrorStr())

if self.CachedEncryptionKey is None:
self.CachedEncryptionKey = str(uuid.uuid4())
Sentry.Info("Database", "Created new encryption key")
result = MoonrakerClient.Get().SendJsonRpcRequest("server.database.post_item",
{
"namespace": "ocotapp",
"key": "public.encryptionKey",
"value": self.CachedEncryptionKey
})
if result.HasError() is False and result.GetResult() is not None:
# Just log. Should be flushed over time.
Sentry.Error("Database", "Failed to set encryption key %s" % result.GetErrorStr())

return self.CachedEncryptionKey


def RemoveAppEntries(self, apps: []):
Sentry.Info("Database", "Removing apps: %s" % apps)
Expand Down Expand Up @@ -121,13 +152,16 @@ def _doContinuouslyAnnouncePresence(self):
"value": {
"pluginVersion": self.PluginVersion,
"lastSeen": time.time(),
"printerId": self.PrinterId
"printerId": self.PrinterId,
"encryptionKey": self.GetOrCreateEncryptionKey()
}
})

if result.HasError():
Sentry.Error("Database", "Ensure database entry item plugin version failed. "+result.GetLoggingErrorStr())
time.sleep(60)
else:
time.sleep(300)
except Exception as e:
Sentry.ExceptionNoSend("Failed to update presence", e)

time.sleep(300)
time.sleep(60)
3 changes: 3 additions & 0 deletions octoapp/appsstorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,6 @@ def RemoveApps(self, apps: [AppInstance]):
Sentry.Debug("APPS", "Removing %s apps" % len(apps))
self.AppStoragePlatformHelper.RemoveApps(apps)
self.LogApps()

def GetOrCreateEncryptionKey(self):
return self.AppStoragePlatformHelper.GetOrCreateEncryptionKey()
26 changes: 25 additions & 1 deletion octoapp/notificationsender.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import requests
import json
import time
import base64
import hashlib
from Crypto.Cipher import AES
from Crypto import Random
from .sentry import Sentry
from .appsstorage import AppStorageHelper

Expand Down Expand Up @@ -211,7 +215,13 @@ def _createAndroidPushData(self, event, state):
"type": type
}

return json.dumps(data)
try:
cipher = AESCipher(AppStorageHelper.Get().GetOrCreateEncryptionKey())
return cipher.encrypt(json.dumps(data))
except Exception as e:
Sentry.ExceptionNoSend(e)
return json.dumps(data)


def _createApnsPushData(self, event, state):
Sentry.Info("SENDER", "Targets contain iOS devices, generating texts for '%s'" % event)
Expand Down Expand Up @@ -455,4 +465,18 @@ def _doContinuouslyUpdateConfig(self):
Sentry.ExceptionNoSend("Failed to fetch config using defaults for 5 minutes", e)
self.CachedConfig = self.DefaultConfig
self.CachedConfigAt = cache_config_max_age + 300

class AESCipher(object):
def __init__(self, key):
self.bs = AES.block_size
self.key = hashlib.sha256(key.encode()).digest()

def encrypt(self, raw):
raw = self._pad(raw)
iv = Random.new().read(AES.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return base64.b64encode(iv + cipher.encrypt(raw.encode())).decode("utf-8")

def _pad(self, s):
return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)

22 changes: 22 additions & 0 deletions octoprint_octoapp/octoprintappstorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import time
import flask
import threading
import uuid
from Crypto import Random
from Crypto.Cipher import AES
from .subplugin import OctoAppSubPlugin
from octoprint.access.permissions import Permissions
from octoprint.events import Events
Expand All @@ -24,6 +27,7 @@ def __init__(self, parent):
self._upgradeDataStructure()
self._upgradeExpirationDate()
self._sendSettingsPluginMessage(self._getAllApps())
self._getOrCreateEncryptionKey()


# !! Platform Command Handler Interface Function !!
Expand Down Expand Up @@ -102,6 +106,24 @@ def OnApiCommand(self, command, data):
else:
return None

# !! Platform Command Handler Interface Function !!
#
# This must receive a lsit of AppInstnace
#
def GetOrCreateEncryptionKey(self):
Sentry.Debug("OCTO STORAGE", "-> GetOrCreateEncryptionKey")
with self.Lock:
Sentry.Debug("OCTO STORAGE", "<- GetOrCreateEncryptionKey")
return self._getOrCreateEncryptionKey()

def _getOrCreateEncryptionKey(self):
key = self.parent._settings.get(["encryptionKey"])
if key is None:
key = str(uuid.uuid4())
Sentry.Info("NOTIFICATION", "Created new encryption key")
self.parent._settings.set(["encryptionKey"], key)
return key


def _getAllApps(self) -> [AppInstance]:
try:
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ rsa>=4.9
dnspython>=2.3.0
httpx==0.24.0
urllib3>=1.26.15,<1.27.0
pycryptodome>=3.15.0
# The following are required only for Moonraker
configparser
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
# urllib3 - There is a bug with parsing headers in versions older than 1.26.? (https://github.com/diyan/pywinrm/issues/269). At least 1.26.6 fixes it, ubt we decide to just stick with a newer version.
#
# Note! These also need to stay in sync with requirements.txt, for the most part they should be the exact same!
plugin_requires = ["pillow", "dnspython>=2.3.0"]
plugin_requires = ["pillow", "dnspython>=2.3.0", "pycryptodome>=3.15.0"]

### --------------------------------------------------------------------------------------------------------------------
### More advanced options that you usually shouldn't have to touch follow after this point
Expand Down

0 comments on commit 02320ce

Please sign in to comment.