Skip to content

Commit

Permalink
Merge pull request #41 from JarbasHiveMind/release-3.1.0a1
Browse files Browse the repository at this point in the history
Release 3.1.0a1
  • Loading branch information
JarbasAl authored Jan 3, 2025
2 parents 530a444 + 74c77cc commit 08cc084
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 59 deletions.
35 changes: 0 additions & 35 deletions .github/workflows/license_tests.yml

This file was deleted.

6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Changelog

## [3.0.3a1](https://github.com/JarbasHiveMind/HiveMind-core/tree/3.0.3a1) (2025-01-02)
## [3.1.0a1](https://github.com/JarbasHiveMind/HiveMind-core/tree/3.1.0a1) (2025-01-03)

[Full Changelog](https://github.com/JarbasHiveMind/HiveMind-core/compare/3.0.2...3.0.3a1)
[Full Changelog](https://github.com/JarbasHiveMind/HiveMind-core/compare/3.0.3...3.1.0a1)

**Merged pull requests:**

- fix:requirements.txt [\#38](https://github.com/JarbasHiveMind/HiveMind-core/pull/38) ([JarbasAl](https://github.com/JarbasAl))
- feat:cipher negotiation [\#40](https://github.com/JarbasHiveMind/HiveMind-core/pull/40) ([JarbasAl](https://github.com/JarbasAl))



Expand Down
8 changes: 8 additions & 0 deletions hivemind_core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@


_DEFAULT = {
# sort encodings by order of preference
"allowed_encodings": ["JSON-Z85B", "JSON-B64", "JSON-HEX"],
"allowed_ciphers": ["CHACHA20-POLY1305", 'AES-GCM'],

# configure various plugins
"agent_protocol": {"module": "hivemind-ovos-agent-plugin",
"hivemind-ovos-agent-plugin": {
"host": "127.0.0.1",
Expand Down Expand Up @@ -33,4 +38,7 @@ def get_server_config() -> JsonStorageXDG:
if not os.path.isfile(db.path):
db.merge(_DEFAULT)
db.store()
for k, v in _DEFAULT.items():
if k not in db:
db[k] = v
return db
73 changes: 56 additions & 17 deletions hivemind_core/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,22 @@
import uuid
from dataclasses import dataclass, field
from enum import Enum, IntEnum
from typing import Union, List, Optional, Callable
from typing import Union, List, Optional, Callable, Literal

import pybase64
from ovos_bus_client import MessageBusClient
from ovos_bus_client.message import Message
from ovos_bus_client.session import Session
from ovos_utils.fakebus import FakeBus
from ovos_utils.log import LOG

from hivemind_core.config import get_server_config
from hivemind_bus_client.identity import NodeIdentity
from hivemind_bus_client.message import HiveMessage, HiveMessageType, HiveMindBinaryPayloadType
from hivemind_bus_client.serialization import decode_bitstring, get_bitstring
from hivemind_bus_client.util import (
decrypt_bin,
encrypt_bin,
decrypt_from_json,
encrypt_as_json,
)
from hivemind_bus_client.encryption import (SupportedEncodings, SupportedCiphers,
decrypt_from_json, encrypt_as_json,
decrypt_bin, encrypt_bin,
_norm_encoding, _norm_cipher)
from hivemind_core.database import ClientDatabase
from hivemind_plugin_manager.protocols import AgentProtocol, BinaryDataHandlerProtocol, ClientCallbacks
from poorman_handshake import HandShake, PasswordHandShake
Expand Down Expand Up @@ -88,6 +86,9 @@ class HiveMindClientConnection:

hm_protocol: Optional['HiveMindListenerProtocol'] = None

cipher: Literal[SupportedCiphers] = SupportedCiphers.AES_GCM
encoding: Literal[SupportedEncodings] = SupportedEncodings.JSON_HEX

def __post_init__(self):
self.handshake = self.handshake or HandShake(self.hm_protocol.identity.private_key)

Expand Down Expand Up @@ -125,15 +126,16 @@ def send(self, message: HiveMessage):
payload=message.payload,
hivemeta=message.metadata,
binary_type=message.bin_type).bytes
LOG.debug(f"unencrypted binary payload: {len(payload)}")
payload = encrypt_bin(self.crypto_key, payload)
LOG.debug(f"unencrypted binary payload size: {len(payload)} bytes")
payload = encrypt_bin(key=self.crypto_key, plaintext=payload, cipher=self.cipher)
is_bin = True
else:
LOG.debug(f"unencrypted payload: {len(message.payload.serialize())}")
LOG.debug(f"unencrypted payload size: {len(message.payload.serialize())} bytes")
payload = encrypt_as_json(
self.crypto_key, message.serialize() # json string
key=self.crypto_key, plaintext=message.serialize(),
cipher=self.cipher, encoding=self.encoding
) # json string
LOG.debug(f"encrypted payload: {len(payload)}")
LOG.debug(f"encrypted payload size: {len(payload)} bytes")
else:
payload = message.serialize()
LOG.debug(f"sent unencrypted!")
Expand All @@ -144,10 +146,12 @@ def decode(self, payload: str) -> HiveMessage:
if self.crypto_key:
# handle binary encryption
if isinstance(payload, bytes):
payload = decrypt_bin(self.crypto_key, payload)
payload = decrypt_bin(key=self.crypto_key, ciphertext=payload,
cipher=self.cipher)
# handle json encryption
elif "ciphertext" in payload:
payload = decrypt_from_json(self.crypto_key, payload)
payload = decrypt_from_json(key=self.crypto_key, ciphertext_json=payload,
encoding=self.encoding, cipher=self.cipher)
else:
LOG.warning("Message was unencrypted")
# TODO - some error if crypto is required
Expand All @@ -167,7 +171,9 @@ def authorize(self, message: Message) -> bool:
return False

# TODO check intent / skill that will trigger
# we want for example to be able to block shutdown/reboot intents to random chat users
# for OVOS agent this is passed in Session and ignored during match
# adding it here allows blocking the utterance completely instead
# or adding a callback for specific agents to decide how to handle
return True


Expand Down Expand Up @@ -255,6 +261,10 @@ def handle_new_client(self, client: HiveMindClientConnection):

needs_handshake = not client.crypto_key and self.handshake_enabled

cfg = get_server_config()
allowed_ciphers = cfg.get("allowed_ciphers") or [SupportedCiphers.AES_GCM]
allowed_encodings = cfg.get("allowed_encodings") or list(SupportedEncodings)

# request client to start handshake (by sending client pubkey)
payload = {
"handshake": needs_handshake, # tell the client it must do a handshake or connection will be dropped
Expand All @@ -266,6 +276,8 @@ def handle_new_client(self, client: HiveMindClientConnection):
"password": client.pswd_handshake
is not None, # is password available (V1 proto, replaces pre-shared key)
"crypto_required": self.require_crypto, # do we allow unencrypted payloads
"encodings": allowed_encodings,
"ciphers": allowed_ciphers
}
msg = HiveMessage(HiveMessageType.HANDSHAKE, payload)
LOG.debug(f"starting {client.peer} HANDSHAKE: {payload}")
Expand Down Expand Up @@ -450,8 +462,32 @@ def handle_handshake_message(
# self.handshake.receive_handshake(payload["envelope"], pub)
# self.crypto_key = self.handshake.secret
elif client.pswd_handshake is not None and "envelope" in message.payload:
# sorted by preference from client
encodings = message.payload.get("encodings") or [SupportedEncodings.JSON_HEX]
encodings = [_norm_encoding(e) for e in encodings]
ciphers = message.payload.get("ciphers") or [SupportedCiphers.AES_GCM]
ciphers = [_norm_cipher(c) for c in ciphers]

# allowed ciphers/encodings defined in config
cfg = get_server_config()
allowed_encodings = cfg.get("allowed_encodings") or list(SupportedEncodings)
allowed_ciphers = cfg.get("allowed_ciphers") or [SupportedCiphers.AES_GCM]

encodings = [e for e in encodings if e in allowed_encodings]
ciphers = [c for c in ciphers if c in allowed_ciphers]
if not ciphers or not encodings:
LOG.warning("Client tried to connect with invalid cipher/encoding")
# TODO - invalid handshake handler
client.disconnect()
return

# from the allowed options, select the one the client prefers
client.cipher = ciphers[0]
client.encoding = encodings[0]

# while the access key is transmitted, the password never is
envelope = message.payload["envelope"]

# TODO - seems tornado never emits these, they never arrive in client
# closing the listener shows futures were never awaited
# until this is debugged force to False
Expand Down Expand Up @@ -481,7 +517,10 @@ def handle_handshake_message(
client.disconnect()
return

msg = HiveMessage(HiveMessageType.HANDSHAKE, {"envelope": envelope_out})
msg = HiveMessage(HiveMessageType.HANDSHAKE,
{"envelope": envelope_out,
"encoding": client.encoding,
"cipher": client.cipher })
client.send(msg) # client can recreate crypto_key on his side now

def handle_hello_message(self, message: HiveMessage, client: HiveMindClientConnection):
Expand Down
6 changes: 3 additions & 3 deletions hivemind_core/version.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# START_VERSION_BLOCK
VERSION_MAJOR = 3
VERSION_MINOR = 0
VERSION_BUILD = 3
VERSION_ALPHA = 0
VERSION_MINOR = 1
VERSION_BUILD = 0
VERSION_ALPHA = 1
# END_VERSION_BLOCK
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ click_default_group
rich
pycryptodomex
poorman-handshake>=1.0.1,<2.0.0
hivemind_bus_client>=0.1.6,<1.0.0
hivemind_bus_client>=0.2.0,<1.0.0
ovos_utils>=0.0.33,<1.0.0
pybase64
hivemind-plugin-manager>=0.3.0,<1.0.0
Expand Down

0 comments on commit 08cc084

Please sign in to comment.