pamoja-session 0.1.18__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.
@@ -0,0 +1,13 @@
1
+ # Native extensions are built per platform, not committed.
2
+ *.so
3
+ *.pyd
4
+ *.dylib
5
+
6
+ # Build and packaging output.
7
+ dist/
8
+ wheels/
9
+ target/
10
+
11
+ # Local virtual environments.
12
+ .venv/
13
+ venv/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anthony Wiedman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.5
2
+ Name: pamoja-session
3
+ Version: 0.1.18
4
+ Summary: X25519 key agreement, HKDF, and ChaCha20-Poly1305 with an anti-replay window, with no TLS stack.
5
+ Project-URL: Repository, https://github.com/molexxxx/pamoja
6
+ Project-URL: Documentation, https://pamoja.molex.cloud/docs/guides/session.html
7
+ Author: molexxxx
8
+ License: MIT
9
+ License-File: LICENSE-MIT
10
+ Keywords: iot,pamoja,robotics,session
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: pamoja-native==0.1.18
17
+ Description-Content-Type: text/markdown
18
+
19
+ # pamoja-session
20
+
21
+ X25519 key agreement, HKDF, and ChaCha20-Poly1305 with an anti-replay window, with no TLS stack. One capability of [pamoja](https://github.com/molexxxx/pamoja), one memory-safe Rust core with bindings for TypeScript, Python, and C#.
22
+
23
+ [![read the guide](https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-guide.svg)](https://pamoja.molex.cloud/docs/guides/session.html)
24
+ [![documentation](https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-docs.svg)](https://pamoja.molex.cloud/docs/)
25
+ [![API reference](https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-api.svg)](https://pamoja.molex.cloud/docs/reference/python/pamoja/session.html)
26
+
27
+ ## Install
28
+
29
+ ```sh
30
+ pip install pamoja-session
31
+ ```
32
+
33
+ ```python
34
+ from pamoja import session
35
+ ```
36
+
37
+ This pulls in `pamoja-native`, the compiled engine. `pip install pamoja` is the whole framework in one package.
38
+
39
+ ## Example
40
+
41
+ The script the test suite runs, spliced here as it ran.
42
+
43
+ From [`bindings/python/guides/session.py`](https://github.com/molexxxx/pamoja/blob/main/bindings/python/guides/session.py):
44
+
45
+ ```python
46
+ import os
47
+
48
+ from pamoja.core import PamojaError
49
+ from pamoja.session import AgreementKey, Role, Session
50
+
51
+ # Each device is provisioned with a 32-byte seed and publishes the key it derives. A real
52
+ # seed comes from the factory or a secure element; any 32 bytes stand in here.
53
+ node = AgreementKey(bytes([7]) * 32)
54
+ gateway = AgreementKey(bytes([9]) * 32)
55
+
56
+ # Neither side sends the session key. Both derive it from the shared secret, a salt that
57
+ # travels in the clear, and both public keys, with opposite roles.
58
+ #
59
+ # The salt must be fresh for every session: reusing one derives the same key from the same
60
+ # pair of devices twice. The initiator draws it and sends it in the clear, so the responder
61
+ # uses the salt it received rather than one of its own.
62
+ salt = os.urandom(16)
63
+ uplink = Session(node, gateway.public_key, salt, Role.INITIATOR)
64
+ downlink = Session(gateway, node.public_key, salt, Role.RESPONDER)
65
+ print("both sides derived a key without sending one")
66
+
67
+ # The pump id is authenticated but not encrypted, so a router still reads it while any
68
+ # change to it fails the tag.
69
+ sealed = uplink.seal(b"flow=41.2", b"pump-3")
70
+ print(f"sealed the reading is no longer readable: {sealed.ciphertext != b'flow=41.2'}")
71
+ print(f"opened {downlink.open(sealed, b'pump-3').decode()}")
72
+
73
+ # The anti-replay window refuses a counter it has already accepted, so a frame captured
74
+ # off the air and sent again is not delivered a second time.
75
+ try:
76
+ downlink.open(sealed, b"pump-3")
77
+ print("a replayed frame was accepted, which should never happen")
78
+ except PamojaError as error:
79
+ print(f"replay refused: {error}")
80
+ ```
81
+
82
+ ## The same capability in every language
83
+
84
+ | Language | Package | Reference |
85
+ | --- | --- | --- |
86
+ | Rust | [`pamoja-session`](https://crates.io/crates/pamoja-session) | [reference](https://pamoja.molex.cloud/docs/reference/rust/pamoja_session/index.html), [docs.rs](https://docs.rs/pamoja-session), [install](https://pamoja.molex.cloud/docs/reference/rust.html#rust-session) |
87
+ | TypeScript | [`@pamoja/session`](https://www.npmjs.com/package/@pamoja/session) | [reference](https://pamoja.molex.cloud/docs/reference/node/modules/_pamoja_session.html), [install](https://pamoja.molex.cloud/docs/reference/node.html#node-session) |
88
+ | Python | [`pamoja-session`](https://pypi.org/project/pamoja-session/) | [reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/session.html), [install](https://pamoja.molex.cloud/docs/reference/python.html#python-session) |
89
+ | C# | [`Pamoja.Session`](https://www.nuget.org/packages/Pamoja.Session) | [reference](https://pamoja.molex.cloud/docs/reference/dotnet/api/Pamoja.Session.html), [install](https://pamoja.molex.cloud/docs/reference/dotnet.html#dotnet-session) |
90
+
91
+ ## Documentation
92
+
93
+ - [`pamoja.session` reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/session.html), every class and function in this module.
94
+ - [The Secured session guide](https://pamoja.molex.cloud/docs/guides/session.html), with the same example in Rust, TypeScript, and C#.
95
+ - [Every capability](https://pamoja.molex.cloud/docs/), and the [install page](https://pamoja.molex.cloud/docs/install.html).
96
+
97
+ ## License
98
+
99
+ MIT
@@ -0,0 +1,81 @@
1
+ # pamoja-session
2
+
3
+ X25519 key agreement, HKDF, and ChaCha20-Poly1305 with an anti-replay window, with no TLS stack. One capability of [pamoja](https://github.com/molexxxx/pamoja), one memory-safe Rust core with bindings for TypeScript, Python, and C#.
4
+
5
+ [![read the guide](https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-guide.svg)](https://pamoja.molex.cloud/docs/guides/session.html)
6
+ [![documentation](https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-docs.svg)](https://pamoja.molex.cloud/docs/)
7
+ [![API reference](https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-api.svg)](https://pamoja.molex.cloud/docs/reference/python/pamoja/session.html)
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ pip install pamoja-session
13
+ ```
14
+
15
+ ```python
16
+ from pamoja import session
17
+ ```
18
+
19
+ This pulls in `pamoja-native`, the compiled engine. `pip install pamoja` is the whole framework in one package.
20
+
21
+ ## Example
22
+
23
+ The script the test suite runs, spliced here as it ran.
24
+
25
+ From [`bindings/python/guides/session.py`](https://github.com/molexxxx/pamoja/blob/main/bindings/python/guides/session.py):
26
+
27
+ ```python
28
+ import os
29
+
30
+ from pamoja.core import PamojaError
31
+ from pamoja.session import AgreementKey, Role, Session
32
+
33
+ # Each device is provisioned with a 32-byte seed and publishes the key it derives. A real
34
+ # seed comes from the factory or a secure element; any 32 bytes stand in here.
35
+ node = AgreementKey(bytes([7]) * 32)
36
+ gateway = AgreementKey(bytes([9]) * 32)
37
+
38
+ # Neither side sends the session key. Both derive it from the shared secret, a salt that
39
+ # travels in the clear, and both public keys, with opposite roles.
40
+ #
41
+ # The salt must be fresh for every session: reusing one derives the same key from the same
42
+ # pair of devices twice. The initiator draws it and sends it in the clear, so the responder
43
+ # uses the salt it received rather than one of its own.
44
+ salt = os.urandom(16)
45
+ uplink = Session(node, gateway.public_key, salt, Role.INITIATOR)
46
+ downlink = Session(gateway, node.public_key, salt, Role.RESPONDER)
47
+ print("both sides derived a key without sending one")
48
+
49
+ # The pump id is authenticated but not encrypted, so a router still reads it while any
50
+ # change to it fails the tag.
51
+ sealed = uplink.seal(b"flow=41.2", b"pump-3")
52
+ print(f"sealed the reading is no longer readable: {sealed.ciphertext != b'flow=41.2'}")
53
+ print(f"opened {downlink.open(sealed, b'pump-3').decode()}")
54
+
55
+ # The anti-replay window refuses a counter it has already accepted, so a frame captured
56
+ # off the air and sent again is not delivered a second time.
57
+ try:
58
+ downlink.open(sealed, b"pump-3")
59
+ print("a replayed frame was accepted, which should never happen")
60
+ except PamojaError as error:
61
+ print(f"replay refused: {error}")
62
+ ```
63
+
64
+ ## The same capability in every language
65
+
66
+ | Language | Package | Reference |
67
+ | --- | --- | --- |
68
+ | Rust | [`pamoja-session`](https://crates.io/crates/pamoja-session) | [reference](https://pamoja.molex.cloud/docs/reference/rust/pamoja_session/index.html), [docs.rs](https://docs.rs/pamoja-session), [install](https://pamoja.molex.cloud/docs/reference/rust.html#rust-session) |
69
+ | TypeScript | [`@pamoja/session`](https://www.npmjs.com/package/@pamoja/session) | [reference](https://pamoja.molex.cloud/docs/reference/node/modules/_pamoja_session.html), [install](https://pamoja.molex.cloud/docs/reference/node.html#node-session) |
70
+ | Python | [`pamoja-session`](https://pypi.org/project/pamoja-session/) | [reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/session.html), [install](https://pamoja.molex.cloud/docs/reference/python.html#python-session) |
71
+ | C# | [`Pamoja.Session`](https://www.nuget.org/packages/Pamoja.Session) | [reference](https://pamoja.molex.cloud/docs/reference/dotnet/api/Pamoja.Session.html), [install](https://pamoja.molex.cloud/docs/reference/dotnet.html#dotnet-session) |
72
+
73
+ ## Documentation
74
+
75
+ - [`pamoja.session` reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/session.html), every class and function in this module.
76
+ - [The Secured session guide](https://pamoja.molex.cloud/docs/guides/session.html), with the same example in Rust, TypeScript, and C#.
77
+ - [Every capability](https://pamoja.molex.cloud/docs/), and the [install page](https://pamoja.molex.cloud/docs/install.html).
78
+
79
+ ## License
80
+
81
+ MIT
@@ -0,0 +1,112 @@
1
+ """Idiomatic secured-session facade.
2
+
3
+ Two devices that already know each other's public keys can agree on a session key
4
+ without ever sending it, and then exchange messages that are confidential, cannot
5
+ be altered undetected, and cannot be replayed. That is the whole of what a small
6
+ device usually needs from transport security, at a fraction of what a TLS stack
7
+ costs it.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import enum
13
+
14
+ from pamoja._native import AgreementKey, SealedMessage
15
+ from pamoja._native import Session as _Session
16
+ from pamoja._native import hkdf_sha256_expand as _hkdf_sha256
17
+ from pamoja._native import hmac_sha256_digest as _hmac_sha256
18
+
19
+ __all__ = [
20
+ "AgreementKey",
21
+ "Role",
22
+ "SealedMessage",
23
+ "Session",
24
+ "hkdf_sha256",
25
+ "hmac_sha256",
26
+ ]
27
+
28
+
29
+ class Role(str, enum.Enum):
30
+ """Which side of a session a device is on.
31
+
32
+ The two devices must choose opposite roles: the role decides the order the
33
+ public keys are mixed in and which direction each side tags its messages
34
+ with, so a session where both sides claim the same role opens nothing.
35
+ """
36
+
37
+ #: The device that opens the session.
38
+ INITIATOR = "Initiator"
39
+ #: The device that answers.
40
+ RESPONDER = "Responder"
41
+
42
+
43
+ class Session:
44
+ """A confidential, tamper-evident, replay-protected channel with one peer."""
45
+
46
+ def __init__(
47
+ self,
48
+ local: AgreementKey,
49
+ peer_public_key: bytes,
50
+ salt: bytes,
51
+ role: Role,
52
+ ) -> None:
53
+ """Establish a session with a peer.
54
+
55
+ :param local: This device's key-agreement secret.
56
+ :param peer_public_key: The peer's 32-byte public key, already
57
+ authenticated by pinning or by a signature.
58
+ :param salt: A fresh per-session salt both sides share, exchanged in the
59
+ clear. Reusing one with the same pair of keys reuses the session key,
60
+ so it must change each session.
61
+ :param role: Whether this device opens the session or answers.
62
+ """
63
+ self._inner = _Session(local, peer_public_key, salt, role.value)
64
+
65
+ def seal(self, plaintext: bytes, aad: bytes = b"") -> SealedMessage:
66
+ """Seal a message for the peer.
67
+
68
+ :param plaintext: The message to protect.
69
+ :param aad: Data authenticated but not encrypted, so it stays readable on
70
+ the wire yet cannot be altered: a device identifier or a routing
71
+ header belongs here.
72
+ :returns: The ciphertext, with the counter and tag to send beside it.
73
+ """
74
+ return self._inner.seal(plaintext, aad)
75
+
76
+ def open(self, sealed: SealedMessage, aad: bytes = b"") -> bytes:
77
+ """Open a message from the peer.
78
+
79
+ :param sealed: The ciphertext with the counter and tag that arrived with
80
+ it.
81
+ :param aad: The same associated data the sender authenticated.
82
+ :returns: The plaintext.
83
+ :raises PamojaError: When the counter repeats or is older than the replay
84
+ window still tracks, and when the tag does not authenticate. Nothing
85
+ readable is ever returned from a message that failed either check.
86
+ """
87
+ return self._inner.open(sealed, aad)
88
+
89
+
90
+ def hmac_sha256(key: bytes, message: bytes) -> bytes:
91
+ """Compute a keyed hash over a message.
92
+
93
+ This is the primitive a host uses to authenticate a pairing exchange or a
94
+ single command, where a whole session would be more than the job needs.
95
+
96
+ :param key: The secret key.
97
+ :param message: The message to authenticate.
98
+ :returns: The 32-byte digest.
99
+ """
100
+ return _hmac_sha256(key, message)
101
+
102
+
103
+ def hkdf_sha256(salt: bytes, ikm: bytes, info: bytes, length: int) -> bytes:
104
+ """Expand input keying material into ``length`` bytes bound to ``info``.
105
+
106
+ :param salt: The salt, which may be empty.
107
+ :param ikm: The input keying material.
108
+ :param info: Context binding the output to its purpose.
109
+ :param length: How many bytes to derive.
110
+ :returns: The derived bytes.
111
+ """
112
+ return _hkdf_sha256(salt, ikm, info, length)
File without changes
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pamoja-session"
7
+ version = "0.1.18"
8
+ description = "X25519 key agreement, HKDF, and ChaCha20-Poly1305 with an anti-replay window, with no TLS stack."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ license-files = ["LICENSE-MIT"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "molexxxx" }]
14
+ keywords = ["pamoja", "iot", "robotics", "session"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Typing :: Typed",
20
+ ]
21
+ dependencies = [
22
+ "pamoja-native==0.1.18",
23
+ ]
24
+
25
+ [project.urls]
26
+ Repository = "https://github.com/molexxxx/pamoja"
27
+ Documentation = "https://pamoja.molex.cloud/docs/guides/session.html"
28
+
29
+ [tool.hatch.build.targets.wheel]
30
+ packages = ["pamoja"]