Skip to content

Commit

Permalink
feat/node_privacy
Browse files Browse the repository at this point in the history
allow messages to be encrypted with a node public PGP key, so messages can be transported without the hive being able to read them

public keys are part of the NodeIdentity
  • Loading branch information
JarbasAl committed May 30, 2024
1 parent 1c9af20 commit 3240af0
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 1 deletion.
46 changes: 45 additions & 1 deletion hivemind_bus_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pyee import EventEmitter
from websocket import ABNF
from websocket import WebSocketApp, WebSocketConnectionClosedException
import pgpy

from hivemind_bus_client.identity import NodeIdentity
from hivemind_bus_client.message import HiveMessage, HiveMessageType
Expand Down Expand Up @@ -271,7 +272,14 @@ def on_message(self, *args):
def _handle_hive_protocol(self, message: HiveMessage):
# LOG.debug(f"received HiveMind message: {message.msg_type}")
if message.msg_type == HiveMessageType.BUS:
self.internal_bus.emit(message.payload)
pload = message.payload
if pload.msg_type == "hive.identity_encrypted":
try:
pload = self.decrypt(pload)
except:
LOG.info("Failed to decrypt PGP message, not for us")
return
self.internal_bus.emit(pload)
self.emitter.emit(message.msg_type, message) # hive message

def emit(self, message: Union[MycroftMessage, HiveMessage]):
Expand Down Expand Up @@ -436,3 +444,39 @@ def wait_for_payload_response(self, message: Union[MycroftMessage, HiveMessage],
# Send message and wait for it's response
self.emit(message)
return waiter.wait(timeout)

# targeted messages for nodes, assymetric encryption
def emit_encrypted(self, message: MycroftMessage, pubkey: Union[str, pgpy.PGPKey]):
message = self.encrypt(message, pubkey)
self.emit(message)

def encrypt(self, message: MycroftMessage, pubkey: Union[str, pgpy.PGPKey]):
if isinstance(pubkey, str):
pubkey, _ = pgpy.PGPKey.from_blob(pubkey)
assert isinstance(pubkey, pgpy.PGPKey)

txt = json.dumps(message.serialize())

text_message = pgpy.PGPMessage.new(txt)
encrypted_message = pubkey.encrypt(text_message)

# sign message
with open(self.identity.private_key, "r") as f:
private_key = pgpy.PGPKey.from_blob(f.read())
# the bitwise OR operator '|' is used to add a signature to a PGPMessage.
encrypted_message |= private_key.sign(encrypted_message,
intended_recipients=[pubkey])

return MycroftMessage("hive.identity_encrypted",
{"ciphertext": str(encrypted_message)})

def decrypt(self, message: MycroftMessage):
assert message.msg_type == "hive.identity_encrypted"
ciphertext = message.data["ciphertext"]
message_from_blob = pgpy.PGPMessage.from_blob(ciphertext)

with open(self.identity.private_key, "r") as f:
private_key = pgpy.PGPKey.from_blob(f.read())

decrypted: str = private_key.decrypt(message_from_blob)
return MycroftMessage.deserialize(json.loads(decrypted))
18 changes: 18 additions & 0 deletions hivemind_bus_client/identity.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from os.path import basename, dirname
from poorman_handshake.asymmetric.utils import export_private_key, create_private_key

from json_database import JsonConfigXDG

Expand All @@ -21,6 +22,15 @@ def name(self):
def name(self, val):
self.IDENTITY_FILE["name"] = val

@property
def public_key(self):
"""ASCI public PGP key"""
return self.IDENTITY_FILE.get("public_key")

@public_key.setter
def public_key(self, val):
self.IDENTITY_FILE["public_key"] = val

@property
def private_key(self):
"""path to PRIVATE .asc PGP key, this cryptographic key
Expand Down Expand Up @@ -81,3 +91,11 @@ def save(self):

def reload(self):
self.IDENTITY_FILE.reload()

def create_keys(self):
key = create_private_key("HiveMindComs")
priv = f"{dirname(self.IDENTITY_FILE.path)}/HiveMindComs.asc"
export_private_key(priv, key)
pub = str(key.pubkey)
self.private_key = priv
self.public_key = pub
12 changes: 12 additions & 0 deletions hivemind_bus_client/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ def identity_set(key: str, password: str, host: str, port: int, siteid: str):
if not host.startswith("ws://") and not host.startswith("wss://"):
host = "ws://" + host
identity.default_master = host
if not identity.private_key:
print("PUBKEY:", identity.public_key)
identity.create_keys()
identity.save()
print(f"identity saved: {identity.IDENTITY_FILE.path}")

Expand Down Expand Up @@ -192,5 +195,14 @@ def test_identity():
node.close()


@hmclient_cmds.command(help="create a PGP key for inter-node communication", name="set-pubkey")
def reset_pubkey():
identity = NodeIdentity()
identity.create_keys()
print("PUBKEY:", identity.public_key)
identity.save()
print(f"identity saved: {identity.IDENTITY_FILE.path}")


if __name__ == "__main__":
hmclient_cmds()

0 comments on commit 3240af0

Please sign in to comment.