fognode 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. fognode-0.2.4/CHANGELOG.md +53 -0
  2. fognode-0.2.4/PKG-INFO +233 -0
  3. fognode-0.2.4/README.md +194 -0
  4. fognode-0.2.4/docs/fognode_connection.svg +108 -0
  5. fognode-0.2.4/examples/headless_server.py +21 -0
  6. {fognode-0.2.2 → fognode-0.2.4}/pyproject.toml +1 -0
  7. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/__init__.py +18 -6
  8. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/app.py +87 -98
  9. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/auth/handshake.py +4 -6
  10. fognode-0.2.4/src/fognode/cipher.py +65 -0
  11. fognode-0.2.4/src/fognode/core/events.py +56 -0
  12. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/core/server.py +27 -9
  13. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/crypto/__init__.py +0 -4
  14. fognode-0.2.4/src/fognode/crypto/channel.py +51 -0
  15. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/crypto/primitives.py +2 -19
  16. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/types/__init__.py +0 -2
  17. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/types/constants.py +1 -3
  18. fognode-0.2.4/src/fognode/utils/ipwords.py +37 -0
  19. {fognode-0.2.2 → fognode-0.2.4}/tests/test_crypto.py +6 -13
  20. {fognode-0.2.2 → fognode-0.2.4}/tests/test_ipwords.py +1 -0
  21. fognode-0.2.2/CHANGELOG.md +0 -27
  22. fognode-0.2.2/PKG-INFO +0 -150
  23. fognode-0.2.2/README.md +0 -112
  24. fognode-0.2.2/examples/headless_server.py +0 -14
  25. fognode-0.2.2/src/fognode/ciphers/__init__.py +0 -57
  26. fognode-0.2.2/src/fognode/ciphers/aesgcm.py +0 -5
  27. fognode-0.2.2/src/fognode/ciphers/blake2.py +0 -14
  28. fognode-0.2.2/src/fognode/ciphers/blowfish.py +0 -32
  29. fognode-0.2.2/src/fognode/ciphers/chacha20.py +0 -23
  30. fognode-0.2.2/src/fognode/ciphers/ed25519.py +0 -23
  31. fognode-0.2.2/src/fognode/ciphers/fernet.py +0 -23
  32. fognode-0.2.2/src/fognode/ciphers/hkdf.py +0 -5
  33. fognode-0.2.2/src/fognode/ciphers/hmac.py +0 -5
  34. fognode-0.2.2/src/fognode/ciphers/pbkdf2.py +0 -5
  35. fognode-0.2.2/src/fognode/ciphers/rsa.py +0 -61
  36. fognode-0.2.2/src/fognode/ciphers/scrypt.py +0 -15
  37. fognode-0.2.2/src/fognode/ciphers/sha3.py +0 -18
  38. fognode-0.2.2/src/fognode/ciphers/x25519.py +0 -5
  39. fognode-0.2.2/src/fognode/crypto/channel.py +0 -82
  40. fognode-0.2.2/src/fognode/utils/ipwords.py +0 -286
  41. {fognode-0.2.2 → fognode-0.2.4}/.github/CODEOWNERS +0 -0
  42. {fognode-0.2.2 → fognode-0.2.4}/.github/PULL_REQUEST_TEMPLATE/bug_fix.md +0 -0
  43. {fognode-0.2.2 → fognode-0.2.4}/.github/PULL_REQUEST_TEMPLATE/optimization.md +0 -0
  44. {fognode-0.2.2 → fognode-0.2.4}/.github/dependabot.yml +0 -0
  45. {fognode-0.2.2 → fognode-0.2.4}/.github/workflows/build.yml +0 -0
  46. {fognode-0.2.2 → fognode-0.2.4}/.github/workflows/pr-summary.yml +0 -0
  47. {fognode-0.2.2 → fognode-0.2.4}/.github/workflows/pr.yml +0 -0
  48. {fognode-0.2.2 → fognode-0.2.4}/.gitignore +0 -0
  49. {fognode-0.2.2 → fognode-0.2.4}/AGENTS.md +0 -0
  50. {fognode-0.2.2 → fognode-0.2.4}/CONTRIBUTING.md +0 -0
  51. {fognode-0.2.2 → fognode-0.2.4}/LICENSE +0 -0
  52. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/__main__.py +0 -0
  53. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/auth/__init__.py +0 -0
  54. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/cli/__init__.py +0 -0
  55. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/cli/entrypoint.py +0 -0
  56. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/core/__init__.py +0 -0
  57. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/core/client.py +0 -0
  58. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/core/probe.py +0 -0
  59. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/crypto/cert.py +0 -0
  60. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/crypto/kdf.py +0 -0
  61. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/crypto/kx.py +0 -0
  62. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/crypto/password.py +0 -0
  63. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/decorators.py +0 -0
  64. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/exceptions.py +0 -0
  65. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/filters/__init__.py +0 -0
  66. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/filters/base.py +0 -0
  67. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/filters/command.py +0 -0
  68. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/filters/text.py +0 -0
  69. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/handlers/__init__.py +0 -0
  70. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/handlers/handler.py +0 -0
  71. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/types/exceptions.py +0 -0
  72. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/types/protocol.py +0 -0
  73. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/utils/__init__.py +0 -0
  74. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/utils/net.py +0 -0
  75. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/utils/ratelimit.py +0 -0
  76. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/wire/__init__.py +0 -0
  77. {fognode-0.2.2 → fognode-0.2.4}/src/fognode/wire/framing.py +0 -0
  78. {fognode-0.2.2 → fognode-0.2.4}/tests/__init__.py +0 -0
  79. {fognode-0.2.2 → fognode-0.2.4}/tests/conftest.py +0 -0
  80. {fognode-0.2.2 → fognode-0.2.4}/tests/test_framing.py +0 -0
  81. {fognode-0.2.2 → fognode-0.2.4}/tests/test_ratelimit.py +0 -0
@@ -0,0 +1,53 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on Keep a Changelog,
6
+ and this project adheres to Semantic Versioning.
7
+
8
+
9
+
10
+ ## [dev] - 2026-05-16
11
+
12
+ ### Added
13
+
14
+ - add event classes in core/events.py ([`18bef8c`](https://github.com/reekeer/fognode/commit/18bef8c63a3ecd4b8b568960a5d46bd31ed6baa0))
15
+ - add on_event decorator and event-driven Server/Client API ([`bf132b5`](https://github.com/reekeer/fognode/commit/bf132b5ee0085e365ddfd8bd2523d2740292aa6c))
16
+
17
+ ### Changed
18
+
19
+ - trigger release workflow on tag push instead of main branch ([`6e4087c`](https://github.com/reekeer/fognode/commit/6e4087c94e76a5fa359445ef1a4e853f4ba8483a))
20
+ - run release on main/tag push only when HEAD is tagged ([`2d6b8f2`](https://github.com/reekeer/fognode/commit/2d6b8f22b067cdf68a115217b83a37dfd47cc1cc))
21
+
22
+ ### Changed
23
+
24
+ - disable local version suffix for PyPI compatibility ([`c539563`](https://github.com/reekeer/fognode/commit/c539563b076afe33985d82e967cb5b21181a3136))
25
+
26
+
27
+
28
+ ## [dev] - 2026-05-16
29
+
30
+ ### Changed
31
+
32
+ - remove chat history and user concept from protocol, server, handshake ([`3c601ed`](https://github.com/reekeer/fognode/commit/3c601eded9dcb68a492042efb338b9921b922618))
33
+ - add Client/Server classes, remove logs, builtin commands, chat state ([`99c74ce`](https://github.com/reekeer/fognode/commit/99c74ce89763dde522415b661d968c1c7ac809bc))
34
+
35
+ ### Changed
36
+
37
+ - auto-release on main push using latest tag ([`a0afea7`](https://github.com/reekeer/fognode/commit/a0afea7183baf99281b6cd4679084942574cd646))
38
+ - add pr summary ([`f733c98`](https://github.com/reekeer/fognode/commit/f733c9870b3c1174f9cb8e35b76a5206187dd9bf))
39
+
40
+
41
+
42
+ # Changelog
43
+
44
+ ## 0.1.0
45
+
46
+ - Initial release.
47
+ - TLS 1.2+ with strong cipher suite.
48
+ - X25519 ephemeral key exchange over TLS.
49
+ - AESGCM-256 + HMAC-SHA256 frame encryption.
50
+ - PBKDF2-HMAC-SHA256 password derivation.
51
+ - Monotonic counter + timestamp replay protection.
52
+ - IP-to-word encoding for human-readable addresses.
53
+ - Rate-limited authentication.
fognode-0.2.4/PKG-INFO ADDED
@@ -0,0 +1,233 @@
1
+ Metadata-Version: 2.4
2
+ Name: fognode
3
+ Version: 0.2.4
4
+ Summary: fognode - headless secure encrypted data transmission. TLS + X25519 + AESGCM + PBKDF2.
5
+ Project-URL: Repository, https://github.com/reekeer/fognode
6
+ Author: reekeer
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 reekeer
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Requires-Python: >=3.10
30
+ Requires-Dist: cryptography>=44.0.0
31
+ Requires-Dist: pycryptodome>=3.20.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: black>=24.0.0; extra == 'dev'
34
+ Requires-Dist: pyright>=1.1.409; extra == 'dev'
35
+ Requires-Dist: pytest-asyncio>=1.3.0; extra == 'dev'
36
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
37
+ Requires-Dist: ruff>=0.5.0; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ <h1 align="center">fognode</h1>
41
+
42
+ <h4 align="center">headless secure encrypted data transmission</h4>
43
+
44
+ <p align="center">
45
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-green?style=for-the-badge&logo=opensourceinitiative&logoColor=FFFFFF" alt="License"></a>
46
+ <img src="https://img.shields.io/badge/Python-3.10%2B-blue?style=for-the-badge&logo=python&logoColor=white" alt="Python">
47
+ <img src="https://img.shields.io/badge/Platform-Linux%20%7C%20macOS%20%7C%20Windows-lightgrey?style=for-the-badge&logo=linux&logoColor=FCC624" alt="Platform">
48
+ <img src="https://img.shields.io/badge/code%20style-black-000000?style=for-the-badge" alt="black">
49
+ <img src="https://img.shields.io/badge/linting-ruff-orange?style=for-the-badge" alt="ruff">
50
+ </p>
51
+
52
+ ---
53
+
54
+ Peer-to-peer encrypted channel library for headless projects. Built for speed and zero-trust networking.
55
+
56
+ ## Security Stack
57
+
58
+ | Layer | Technology |
59
+ |---|---|
60
+ | Transport | TLS 1.2+ with self-signed RSA-4096 certs |
61
+ | Key Exchange | X25519 ephemeral ECDH |
62
+ | Session Keys | HKDF-SHA256 |
63
+ | Channel Cipher | mini-MTProto (AES-256-IGE + SHA-256 msg_key) |
64
+ | Password Derivation | PBKDF2-HMAC-SHA256 (390k iterations) |
65
+ | Auth Challenge | HMAC-SHA256 over random nonce |
66
+ | Cert Pinning | SHA-256 fingerprint verification |
67
+
68
+ ## How connection works
69
+
70
+ <p align="center">
71
+ <img src="./docs/fognode_connection.svg" alt="fognode connection flow" width="900"/>
72
+ </p>
73
+
74
+ ## Install
75
+
76
+ ```bash
77
+ pip install fognode
78
+ ```
79
+
80
+ ## Quick Start
81
+
82
+ ### Server
83
+
84
+ ```python
85
+ from fognode import Server, MessageEvent, ConnectEvent, DisconnectEvent
86
+
87
+ server = Server(host="0.0.0.0", port=9443, password="secret")
88
+
89
+ @server.on_event(MessageEvent)
90
+ async def on_message(ctx):
91
+ if ctx.event.text:
92
+ await ctx.answer(f"echo: {ctx.event.text}")
93
+
94
+ @server.on_event(ConnectEvent)
95
+ async def on_connect(ctx):
96
+ print("+ peer connected")
97
+
98
+ @server.on_event(DisconnectEvent)
99
+ async def on_disconnect(ctx):
100
+ print("- peer disconnected")
101
+
102
+ if __name__ == "__main__":
103
+ server.run()
104
+ ```
105
+
106
+ ### Client
107
+
108
+ ```python
109
+ from fognode import Client, MessageEvent, ClosedEvent, ErrorEvent
110
+
111
+ client = Client(connect_string="oak-pine-stone-field:9443", password="secret")
112
+
113
+ @client.on_event(MessageEvent)
114
+ async def on_message(ctx):
115
+ print(f"msg: {ctx.event.text}")
116
+
117
+ @client.on_event(ClosedEvent)
118
+ async def on_closed(ctx):
119
+ print("connection closed")
120
+
121
+ @client.on_event(ErrorEvent)
122
+ async def on_error(ctx):
123
+ print(f"error: {ctx.event.exception}")
124
+
125
+ if __name__ == "__main__":
126
+ client.connect()
127
+ ```
128
+
129
+ ### Classic API
130
+
131
+ ```python
132
+ from fognode import start_server, client_connect
133
+
134
+ ip, code, fp = start_server("0.0.0.0", 9443, "secret")
135
+ print(f"Server running at {code}:9443")
136
+ ```
137
+
138
+ ## Events
139
+
140
+ | Event | Server | Client | Description |
141
+ |---|---|---|---|
142
+ | `StartEvent` | yes | yes | Server/client started |
143
+ | `ConnectEvent` | yes | yes | Peer connected |
144
+ | `DisconnectEvent` | yes | yes | Peer disconnected |
145
+ | `MessageEvent` | yes | yes | Message received (`type`, `text`, `ts`) |
146
+ | `ClosedEvent` | no | yes | Connection closed |
147
+ | `ErrorEvent` | no | yes | Error occurred (`exception`) |
148
+
149
+ ### Decorators
150
+
151
+ ```python
152
+ # Specific event
153
+ @server.on_event(MessageEvent)
154
+ async def handler(ctx):
155
+ pass
156
+
157
+ # Aliases
158
+ @server.on_connect()
159
+ @server.on_disconnect()
160
+ @server.on_message()
161
+ @client.on_closed()
162
+ @client.on_error()
163
+ ```
164
+
165
+ ## How mini-MTProto Works
166
+
167
+ Each message is encrypted independently using a simplified MTProto 2.0 scheme:
168
+
169
+ 1. **Serialize** JSON payload to bytes
170
+ 2. **Pad** to AES block size (16 bytes)
171
+ 3. **Compute msg_key** = `SHA-256(padded)[8:24]` (128 bits)
172
+ 4. **Derive AES key** = `SHA-256(auth_key + msg_key)`
173
+ 5. **Derive AES IV** = `SHA-256(msg_key + auth_key)`
174
+ 6. **Encrypt** with AES-256-IGE
175
+ 7. **Send** `length(4 bytes) + msg_key(16 bytes) + ciphertext`
176
+
177
+ Decryption reverses the process and verifies `msg_key` integrity.
178
+
179
+ ## CLI
180
+
181
+ ```bash
182
+ # Run server
183
+ fognode server --host 0.0.0.0 --port 9443 --password secret
184
+
185
+ # Interactive client
186
+ fognode client oak-pine-stone-field:9443 --password secret
187
+
188
+ # Probe TLS fingerprint (no auth)
189
+ fognode probe oak-pine-stone-field:9443
190
+
191
+ # Server status via authenticated channel
192
+ fognode status oak-pine-stone-field:9443 --password secret
193
+
194
+ # Send one message and exit
195
+ fognode send oak-pine-stone-field:9443 --password secret --text "hello"
196
+
197
+ # Monitor messages only
198
+ fognode monitor oak-pine-stone-field:9443 --password secret
199
+
200
+ # Show certificate info
201
+ fognode cert --file fognode_cert.pem
202
+ ```
203
+
204
+ ## Project Structure
205
+
206
+ ```
207
+ src/fognode/
208
+ ├── app.py # Server, Client, Context
209
+ ├── cipher.py # mini-MTProto AES-256-IGE encrypt/decrypt
210
+ ├── cli/
211
+ │ └── entrypoint.py # argparse CLI
212
+ ├── core/
213
+ │ ├── client.py # client_connect()
214
+ │ ├── events.py # Event classes
215
+ │ ├── probe.py # probe_server()
216
+ │ └── server.py # start_server()
217
+ ├── crypto/
218
+ │ ├── cert.py # Self-signed cert generation
219
+ │ ├── channel.py # SecureChannel (mini-MTProto)
220
+ │ ├── kdf.py # Session key helpers
221
+ │ ├── kx.py # X25519 keypair + shared secret
222
+ │ ├── password.py # PBKDF2 password store
223
+ │ └── primitives.py # pbkdf2, hmac256, hkdf_expand
224
+ ├── filters/ # BaseFilter, Command, Text
225
+ ├── handlers/ # HandlerObject
226
+ ├── types/ # constants, exceptions, protocol
227
+ ├── utils/ # ipwords, ratelimit, net
228
+ └── wire/ # Length-prefixed JSON framing
229
+ ```
230
+
231
+ ---
232
+
233
+ <p align="center"><sub><a href="LICENSE">MIT</a> © reekeer</sub></p>
@@ -0,0 +1,194 @@
1
+ <h1 align="center">fognode</h1>
2
+
3
+ <h4 align="center">headless secure encrypted data transmission</h4>
4
+
5
+ <p align="center">
6
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-green?style=for-the-badge&logo=opensourceinitiative&logoColor=FFFFFF" alt="License"></a>
7
+ <img src="https://img.shields.io/badge/Python-3.10%2B-blue?style=for-the-badge&logo=python&logoColor=white" alt="Python">
8
+ <img src="https://img.shields.io/badge/Platform-Linux%20%7C%20macOS%20%7C%20Windows-lightgrey?style=for-the-badge&logo=linux&logoColor=FCC624" alt="Platform">
9
+ <img src="https://img.shields.io/badge/code%20style-black-000000?style=for-the-badge" alt="black">
10
+ <img src="https://img.shields.io/badge/linting-ruff-orange?style=for-the-badge" alt="ruff">
11
+ </p>
12
+
13
+ ---
14
+
15
+ Peer-to-peer encrypted channel library for headless projects. Built for speed and zero-trust networking.
16
+
17
+ ## Security Stack
18
+
19
+ | Layer | Technology |
20
+ |---|---|
21
+ | Transport | TLS 1.2+ with self-signed RSA-4096 certs |
22
+ | Key Exchange | X25519 ephemeral ECDH |
23
+ | Session Keys | HKDF-SHA256 |
24
+ | Channel Cipher | mini-MTProto (AES-256-IGE + SHA-256 msg_key) |
25
+ | Password Derivation | PBKDF2-HMAC-SHA256 (390k iterations) |
26
+ | Auth Challenge | HMAC-SHA256 over random nonce |
27
+ | Cert Pinning | SHA-256 fingerprint verification |
28
+
29
+ ## How connection works
30
+
31
+ <p align="center">
32
+ <img src="./docs/fognode_connection.svg" alt="fognode connection flow" width="900"/>
33
+ </p>
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install fognode
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ### Server
44
+
45
+ ```python
46
+ from fognode import Server, MessageEvent, ConnectEvent, DisconnectEvent
47
+
48
+ server = Server(host="0.0.0.0", port=9443, password="secret")
49
+
50
+ @server.on_event(MessageEvent)
51
+ async def on_message(ctx):
52
+ if ctx.event.text:
53
+ await ctx.answer(f"echo: {ctx.event.text}")
54
+
55
+ @server.on_event(ConnectEvent)
56
+ async def on_connect(ctx):
57
+ print("+ peer connected")
58
+
59
+ @server.on_event(DisconnectEvent)
60
+ async def on_disconnect(ctx):
61
+ print("- peer disconnected")
62
+
63
+ if __name__ == "__main__":
64
+ server.run()
65
+ ```
66
+
67
+ ### Client
68
+
69
+ ```python
70
+ from fognode import Client, MessageEvent, ClosedEvent, ErrorEvent
71
+
72
+ client = Client(connect_string="oak-pine-stone-field:9443", password="secret")
73
+
74
+ @client.on_event(MessageEvent)
75
+ async def on_message(ctx):
76
+ print(f"msg: {ctx.event.text}")
77
+
78
+ @client.on_event(ClosedEvent)
79
+ async def on_closed(ctx):
80
+ print("connection closed")
81
+
82
+ @client.on_event(ErrorEvent)
83
+ async def on_error(ctx):
84
+ print(f"error: {ctx.event.exception}")
85
+
86
+ if __name__ == "__main__":
87
+ client.connect()
88
+ ```
89
+
90
+ ### Classic API
91
+
92
+ ```python
93
+ from fognode import start_server, client_connect
94
+
95
+ ip, code, fp = start_server("0.0.0.0", 9443, "secret")
96
+ print(f"Server running at {code}:9443")
97
+ ```
98
+
99
+ ## Events
100
+
101
+ | Event | Server | Client | Description |
102
+ |---|---|---|---|
103
+ | `StartEvent` | yes | yes | Server/client started |
104
+ | `ConnectEvent` | yes | yes | Peer connected |
105
+ | `DisconnectEvent` | yes | yes | Peer disconnected |
106
+ | `MessageEvent` | yes | yes | Message received (`type`, `text`, `ts`) |
107
+ | `ClosedEvent` | no | yes | Connection closed |
108
+ | `ErrorEvent` | no | yes | Error occurred (`exception`) |
109
+
110
+ ### Decorators
111
+
112
+ ```python
113
+ # Specific event
114
+ @server.on_event(MessageEvent)
115
+ async def handler(ctx):
116
+ pass
117
+
118
+ # Aliases
119
+ @server.on_connect()
120
+ @server.on_disconnect()
121
+ @server.on_message()
122
+ @client.on_closed()
123
+ @client.on_error()
124
+ ```
125
+
126
+ ## How mini-MTProto Works
127
+
128
+ Each message is encrypted independently using a simplified MTProto 2.0 scheme:
129
+
130
+ 1. **Serialize** JSON payload to bytes
131
+ 2. **Pad** to AES block size (16 bytes)
132
+ 3. **Compute msg_key** = `SHA-256(padded)[8:24]` (128 bits)
133
+ 4. **Derive AES key** = `SHA-256(auth_key + msg_key)`
134
+ 5. **Derive AES IV** = `SHA-256(msg_key + auth_key)`
135
+ 6. **Encrypt** with AES-256-IGE
136
+ 7. **Send** `length(4 bytes) + msg_key(16 bytes) + ciphertext`
137
+
138
+ Decryption reverses the process and verifies `msg_key` integrity.
139
+
140
+ ## CLI
141
+
142
+ ```bash
143
+ # Run server
144
+ fognode server --host 0.0.0.0 --port 9443 --password secret
145
+
146
+ # Interactive client
147
+ fognode client oak-pine-stone-field:9443 --password secret
148
+
149
+ # Probe TLS fingerprint (no auth)
150
+ fognode probe oak-pine-stone-field:9443
151
+
152
+ # Server status via authenticated channel
153
+ fognode status oak-pine-stone-field:9443 --password secret
154
+
155
+ # Send one message and exit
156
+ fognode send oak-pine-stone-field:9443 --password secret --text "hello"
157
+
158
+ # Monitor messages only
159
+ fognode monitor oak-pine-stone-field:9443 --password secret
160
+
161
+ # Show certificate info
162
+ fognode cert --file fognode_cert.pem
163
+ ```
164
+
165
+ ## Project Structure
166
+
167
+ ```
168
+ src/fognode/
169
+ ├── app.py # Server, Client, Context
170
+ ├── cipher.py # mini-MTProto AES-256-IGE encrypt/decrypt
171
+ ├── cli/
172
+ │ └── entrypoint.py # argparse CLI
173
+ ├── core/
174
+ │ ├── client.py # client_connect()
175
+ │ ├── events.py # Event classes
176
+ │ ├── probe.py # probe_server()
177
+ │ └── server.py # start_server()
178
+ ├── crypto/
179
+ │ ├── cert.py # Self-signed cert generation
180
+ │ ├── channel.py # SecureChannel (mini-MTProto)
181
+ │ ├── kdf.py # Session key helpers
182
+ │ ├── kx.py # X25519 keypair + shared secret
183
+ │ ├── password.py # PBKDF2 password store
184
+ │ └── primitives.py # pbkdf2, hmac256, hkdf_expand
185
+ ├── filters/ # BaseFilter, Command, Text
186
+ ├── handlers/ # HandlerObject
187
+ ├── types/ # constants, exceptions, protocol
188
+ ├── utils/ # ipwords, ratelimit, net
189
+ └── wire/ # Length-prefixed JSON framing
190
+ ```
191
+
192
+ ---
193
+
194
+ <p align="center"><sub><a href="LICENSE">MIT</a> © reekeer</sub></p>
@@ -0,0 +1,108 @@
1
+ <svg width="900" height="540" viewBox="0 0 900 540" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <marker id="a" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
4
+ <path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
5
+ </marker>
6
+ <style>
7
+ text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; }
8
+ .lbl { font-size: 13px; fill: #24292f; }
9
+ .sec { font-size: 12px; fill: #8c959f; }
10
+ .hdr { font-size: 15px; font-weight: 600; fill: #24292f; }
11
+ .note { font-size: 12px; fill: #57606a; }
12
+ @media (prefers-color-scheme: dark) {
13
+ .lbl { fill: #e6edf3; }
14
+ .hdr { fill: #f0f6fc; }
15
+ .note { fill: #8b949e; }
16
+ .sec { fill: #6e7681; }
17
+ .box { fill: #21262d !important; stroke: #30363d !important; }
18
+ .pill { fill: #161b22 !important; stroke: #30363d !important; }
19
+ .pgray{ fill: #21262d !important; stroke: #30363d !important; }
20
+ .life { stroke: #30363d !important; }
21
+ .div { stroke: #21262d !important; }
22
+ .arr { stroke: #8b949e !important; }
23
+ .arrb { stroke: #e6edf3 !important; }
24
+ }
25
+ </style>
26
+ </defs>
27
+
28
+ <!-- center=450, pill w=360, x=270, x2=630 -->
29
+ <!-- server lifeline x=100, client lifeline x=800 -->
30
+
31
+ <rect class="box" x="40" y="28" width="120" height="40" rx="8" fill="#f6f8fa" stroke="#d0d7de" stroke-width="1"/>
32
+ <text class="hdr" x="100" y="53" text-anchor="middle">Server</text>
33
+
34
+ <rect class="box" x="740" y="28" width="120" height="40" rx="8" fill="#f6f8fa" stroke="#d0d7de" stroke-width="1"/>
35
+ <text class="hdr" x="800" y="53" text-anchor="middle">Client</text>
36
+
37
+ <line class="life" x1="100" y1="68" x2="100" y2="510" stroke="#d0d7de" stroke-width="1" stroke-dasharray="5 4"/>
38
+ <line class="life" x1="800" y1="68" x2="800" y2="510" stroke="#d0d7de" stroke-width="1" stroke-dasharray="5 4"/>
39
+
40
+ <!-- TLS -->
41
+ <text class="sec" x="450" y="96" text-anchor="middle">TLS</text>
42
+ <line class="div" x1="102" y1="92" x2="428" y2="92" stroke="#e0e0e0" stroke-width="0.5"/>
43
+ <line class="div" x1="472" y1="92" x2="798" y2="92" stroke="#e0e0e0" stroke-width="0.5"/>
44
+
45
+ <line class="arr" x1="798" y1="116" x2="632" y2="116" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
46
+ <rect class="pill" x="270" y="105" width="360" height="24" rx="5" fill="#fff" stroke="#d0d7de" stroke-width="0.5"/>
47
+ <line class="arr" x1="268" y1="116" x2="102" y2="116" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
48
+ <text class="lbl" x="450" y="121" text-anchor="middle">TCP connect</text>
49
+
50
+ <line class="arr" x1="102" y1="152" x2="268" y2="152" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
51
+ <rect class="pill" x="270" y="141" width="360" height="24" rx="5" fill="#fff" stroke="#d0d7de" stroke-width="0.5"/>
52
+ <line class="arr" x1="630" y1="152" x2="798" y2="152" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
53
+ <text class="lbl" x="450" y="157" text-anchor="middle">TLS 1.2+ handshake — RSA-4096 self-signed cert</text>
54
+
55
+ <line class="arr" x1="798" y1="188" x2="632" y2="188" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
56
+ <rect class="pill" x="270" y="177" width="360" height="24" rx="5" fill="#fff" stroke="#d0d7de" stroke-width="0.5"/>
57
+ <line class="arr" x1="268" y1="188" x2="102" y2="188" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
58
+ <text class="lbl" x="450" y="193" text-anchor="middle">SHA-256 cert fingerprint verify</text>
59
+
60
+ <!-- Key exchange -->
61
+ <text class="sec" x="450" y="220" text-anchor="middle">Key exchange</text>
62
+ <line class="div" x1="102" y1="216" x2="390" y2="216" stroke="#e0e0e0" stroke-width="0.5"/>
63
+ <line class="div" x1="510" y1="216" x2="798" y2="216" stroke="#e0e0e0" stroke-width="0.5"/>
64
+
65
+ <line class="arr" x1="798" y1="240" x2="632" y2="240" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
66
+ <rect class="pill" x="270" y="229" width="360" height="24" rx="5" fill="#fff" stroke="#d0d7de" stroke-width="0.5"/>
67
+ <line class="arr" x1="268" y1="240" x2="102" y2="240" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
68
+ <text class="lbl" x="450" y="245" text-anchor="middle">X25519 ephemeral public key</text>
69
+
70
+ <line class="arr" x1="102" y1="276" x2="268" y2="276" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
71
+ <rect class="pill" x="270" y="265" width="360" height="24" rx="5" fill="#fff" stroke="#d0d7de" stroke-width="0.5"/>
72
+ <line class="arr" x1="630" y1="276" x2="798" y2="276" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
73
+ <text class="lbl" x="450" y="281" text-anchor="middle">X25519 ephemeral public key</text>
74
+
75
+ <rect class="pgray" x="270" y="296" width="360" height="24" rx="5" fill="#f6f8fa" stroke="#ccc" stroke-width="0.5"/>
76
+ <text class="note" x="450" y="312" text-anchor="middle">both sides derive: ECDH → HKDF-SHA256 → session key</text>
77
+
78
+ <!-- Auth -->
79
+ <text class="sec" x="450" y="344" text-anchor="middle">Auth</text>
80
+ <line class="div" x1="102" y1="340" x2="424" y2="340" stroke="#e0e0e0" stroke-width="0.5"/>
81
+ <line class="div" x1="476" y1="340" x2="798" y2="340" stroke="#e0e0e0" stroke-width="0.5"/>
82
+
83
+ <line class="arr" x1="102" y1="364" x2="268" y2="364" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
84
+ <rect class="pill" x="270" y="353" width="360" height="24" rx="5" fill="#fff" stroke="#d0d7de" stroke-width="0.5"/>
85
+ <line class="arr" x1="630" y1="364" x2="798" y2="364" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
86
+ <text class="lbl" x="450" y="369" text-anchor="middle">random nonce</text>
87
+
88
+ <line class="arr" x1="798" y1="400" x2="632" y2="400" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
89
+ <rect class="pill" x="270" y="389" width="360" height="24" rx="5" fill="#fff" stroke="#d0d7de" stroke-width="0.5"/>
90
+ <line class="arr" x1="268" y1="400" x2="102" y2="400" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
91
+ <text class="lbl" x="450" y="405" text-anchor="middle">HMAC-SHA256(nonce, PBKDF2 password)</text>
92
+
93
+ <line class="arr" x1="102" y1="436" x2="268" y2="436" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
94
+ <rect class="pill" x="270" y="425" width="360" height="24" rx="5" fill="#fff" stroke="#d0d7de" stroke-width="0.5"/>
95
+ <line class="arr" x1="630" y1="436" x2="798" y2="436" stroke="#555" stroke-width="1.2" marker-end="url(#a)"/>
96
+ <text class="lbl" x="450" y="441" text-anchor="middle">auth OK / reject</text>
97
+
98
+ <!-- Messaging -->
99
+ <text class="sec" x="450" y="466" text-anchor="middle">Messaging</text>
100
+ <line class="div" x1="102" y1="462" x2="396" y2="462" stroke="#e0e0e0" stroke-width="0.5"/>
101
+ <line class="div" x1="504" y1="462" x2="798" y2="462" stroke="#e0e0e0" stroke-width="0.5"/>
102
+
103
+ <line class="arrb" x1="798" y1="490" x2="632" y2="490" stroke="#24292f" stroke-width="1.5" marker-end="url(#a)"/>
104
+ <rect class="pill" x="270" y="479" width="360" height="24" rx="5" fill="#fff" stroke="#8c959f" stroke-width="0.8"/>
105
+ <line class="arrb" x1="268" y1="490" x2="102" y2="490" stroke="#24292f" stroke-width="1.5" marker-end="url(#a)"/>
106
+ <text class="lbl" x="450" y="495" text-anchor="middle" style="font-weight:600">mini-MTProto · AES-256-IGE · ↻ until disconnect</text>
107
+
108
+ </svg>
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from fognode import Server, ConnectEvent, DisconnectEvent, MessageEvent
4
+
5
+ server = Server(host="0.0.0.0", port=9443, password="secret")
6
+
7
+ @server.on_event(ConnectEvent)
8
+ async def on_connect(ctx):
9
+ print("+ peer connected")
10
+
11
+ @server.on_event(DisconnectEvent)
12
+ async def on_disconnect(ctx):
13
+ print("- peer disconnected")
14
+
15
+ @server.on_event(MessageEvent)
16
+ async def on_message(ctx):
17
+ if ctx.event.text:
18
+ await ctx.answer(f"echo: {ctx.event.text}")
19
+
20
+ if __name__ == "__main__":
21
+ server.run()
@@ -17,6 +17,7 @@ mainteiners = [
17
17
  ]
18
18
  dependencies = [
19
19
  "cryptography>=44.0.0",
20
+ "pycryptodome>=3.20.0",
20
21
  ]
21
22
 
22
23
  [project.urls]