diff --git a/python/README.md b/python/README.md index bf9745ec..81a44e71 100644 --- a/python/README.md +++ b/python/README.md @@ -78,6 +78,59 @@ async def dht_async_demo(key_str: str): asyncio.run(dht_async_demo("unique_key")) ``` +## Identity Persistence + +Keys can be imported from PEM or DER bytes. Private keys export PKCS#8 PEM; +public keys export DER. Certificates export their complete PEM chain. + +```python +import opendht as dht + +identity = dht.Identity.generate("my-node") + +private_key_pem = identity.key.serialize(password) +certificate_pem = bytes(identity.certificate) +public_key_der = bytes(identity.publickey) + +restored = dht.Identity( + dht.PrivateKey(private_key_pem, password), + dht.Certificate(certificate_pem), +) +public_key = dht.PublicKey(public_key_der) +node = dht.DhtRunner() +node.run(id=restored) +``` + +Here `password` is an application-supplied string. Save the exported bytes using +your application's storage, protect private key files, and keep the password +separate from the key. `bytes(private_key)` and `private_key.serialize()` without +a password export **unencrypted** private keys. Empty or malformed key data, +incorrect passwords, and passwords containing NUL characters raise exceptions. +Exporting an uninitialized key raises `ValueError`. + +## Value Metadata + +The following properties are readable and writable on `Value`, including values +returned by `get()` and `listen()`: + +| Property | Type | Meaning | +|----------|------|---------| +| `type` | unsigned 16-bit integer | Native value type identifier | +| `seq` | unsigned 16-bit integer | Sequence number for signed value updates | +| `priority` | unsigned integer | Push priority: `0` high, `1` normal | +| `pushType` | string | Push notification type, such as `audioCall` | + +```python +value = dht.Value(b"some binary data") +value.type = 42 +value.seq = 1 +value.priority = 1 +value.pushType = "audioCall" +``` + +Set metadata before publishing the value. Numeric values outside the native +integer range raise `OverflowError`. + ## Documentation For more information, examples and documentation, or to repport issues, visit: https://opendht.net diff --git a/python/opendht.pyx b/python/opendht.pyx index 9944db33..4c3677e6 100644 --- a/python/opendht.pyx +++ b/python/opendht.pyx @@ -324,6 +324,30 @@ cdef class Value(object): def id(self, cpp.uint64_t value): self._value.get().id = value @property + def type(self) -> int: + return self._value.get().type + @type.setter + def type(self, cpp.uint16_t value): + self._value.get().type = value + @property + def seq(self) -> int: + return self._value.get().seq + @seq.setter + def seq(self, cpp.uint16_t value): + self._value.get().seq = value + @property + def priority(self) -> int: + return self._value.get().priority + @priority.setter + def priority(self, unsigned value): + self._value.get().priority = value + @property + def pushType(self) -> str: + return self._value.get().pushType.decode() + @pushType.setter + def pushType(self, str value): + self._value.get().pushType = value.encode() + @property def size(self) -> int: return self._value.get().size() @@ -383,6 +407,30 @@ cdef class NodeSet(object): cdef class PrivateKey(_WithPk): cdef shared_ptr[cpp.PrivateKey] _key + def __init__(self, bytes data=None, str password=""): + cdef cpp.Blob encoded + if '\0' in password: + raise ValueError("Password must not contain NUL characters") + if data is not None: + if not data: + raise ValueError("Private key data must not be empty") + encoded = data + self._key = cpp.make_shared[cpp.PrivateKey](encoded, password.encode()) + + def serialize(self, str password="") -> bytes: + """Export PKCS#8 PEM, encrypted when a nonempty password is supplied.""" + if not self._key: + raise ValueError("PrivateKey is not initialized") + if '\0' in password: + raise ValueError("Password must not contain NUL characters") + cdef cpp.Blob encoded = self._key.get().serialize(password.encode()) + if encoded.empty(): + raise RuntimeError("Could not export private key") + return (encoded.data())[:encoded.size()] + + def __bytes__(self): + return self.serialize() + def getId(self) -> InfoHash: if not self._key: raise ValueError("PrivateKey is not initialized") @@ -441,6 +489,25 @@ cdef class PrivateKey(_WithPk): cdef class PublicKey(_WithPk): cdef cpp.shared_ptr[cpp.PublicKey] _key + def __init__(self, bytes data=None): + cdef cpp.Blob encoded + if data is not None: + if not data: + raise ValueError("Public key data must not be empty") + encoded = data + self._key = cpp.make_shared[cpp.PublicKey](encoded) + + def serialize(self) -> bytes: + """Export the public key as DER bytes.""" + if not self._key: + raise ValueError("PublicKey is not initialized") + cdef cpp.Blob encoded + self._key.get().pack(encoded) + return (encoded.data())[:encoded.size()] + + def __bytes__(self): + return self.serialize() + def getId(self) -> InfoHash: if not self._key: raise ValueError("PublicKey is not initialized") @@ -512,7 +579,7 @@ cdef class Certificate(_WithPk): raise ValueError("PrivateKey is not initialized") self._cert.get().revoke(deref(k._key.get()), deref(c._cert.get())); def __bytes__(self): - return self._cert.get().toString().encode() if self._cert else b'' + return self._cert.get().toString() if self._cert else b'' @property def issuer(self) -> Certificate|None: if self._cert and self._cert.get().issuer: diff --git a/python/opendht_cpp.pxd b/python/opendht_cpp.pxd index 62ab955a..932cc5e1 100644 --- a/python/opendht_cpp.pxd +++ b/python/opendht_cpp.pxd @@ -104,7 +104,9 @@ cdef extern from "opendht/crypto.h" namespace "dht::crypto": cdef cppclass PrivateKey: PrivateKey() except + + PrivateKey(Blob data, string password) except + shared_ptr[PublicKey] getSharedPublicKey() except + + Blob serialize(string password) except + Blob decrypt(Blob data) except + Blob sign(Blob data) except + @staticmethod @@ -114,8 +116,10 @@ cdef extern from "opendht/crypto.h" namespace "dht::crypto": cdef cppclass PublicKey: PublicKey() except + + PublicKey(Blob data) except + InfoHash getId() except + PkId getLongId() except + + void pack(Blob& data) except + Blob encrypt(Blob data) except + bool checkSignature(Blob data, Blob signature) except + bool operator==(PublicKey) const @@ -168,6 +172,10 @@ cdef extern from "opendht/value.h" namespace "dht": InfoHash recipient vector[uint8_t] data string user_type + uint16_t type + uint16_t seq + unsigned priority + string pushType cdef cppclass ValueType: ValueType(uint16_t id, string name, seconds expiration) except +