From 1ac4aefdc82bfc72139b928c190a312f14221384 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Tue, 30 Dec 2025 01:43:23 +0100 Subject: [PATCH] =?UTF-8?q?=09ge=C3=A4ndert:=20=20=20=20=20=20=20README.md?= =?UTF-8?q?=20=09neue=20Datei:=20=20=20=20=20client/client.py=20=09neue=20?= =?UTF-8?q?Datei:=20=20=20=20=20client/sec/client.crt.pem=20=09neue=20Date?= =?UTF-8?q?i:=20=20=20=20=20client/sec/client.csr.pem=20=09neue=20Datei:?= =?UTF-8?q?=20=20=20=20=20client/sec/client.key.pem=20=09neue=20Datei:=20?= =?UTF-8?q?=20=20=20=20container/Dockerfile=20=09neue=20Datei:=20=20=20=20?= =?UTF-8?q?=20container/requirements.txt=20=09neue=20Datei:=20=20=20=20=20?= =?UTF-8?q?lib/=5F=5Fpycache=5F=5F/crypto=5Futils.cpython-313.pyc=20=09neu?= =?UTF-8?q?e=20Datei:=20=20=20=20=20lib/=5F=5Fpycache=5F=5F/jebp=5Futils.c?= =?UTF-8?q?python-313.pyc=20=09neue=20Datei:=20=20=20=20=20lib/=5F=5Fpycac?= =?UTF-8?q?he=5F=5F/terminal=5Ftable.cpython-313.pyc=20=09neue=20Datei:=20?= =?UTF-8?q?=20=20=20=20lib/crypto=5Futils.py=20=09neue=20Datei:=20=20=20?= =?UTF-8?q?=20=20lib/jebp=5Futils.py=20=09neue=20Datei:=20=20=20=20=20lib/?= =?UTF-8?q?terminal=5Ftable.py=20=09neue=20Datei:=20=20=20=20=20server/cli?= =?UTF-8?q?ents=5Fmanagement/chclient.py=20=09neue=20Datei:=20=20=20=20=20?= =?UTF-8?q?server/clients=5Fmanagement/lsclients.py=20=09neue=20Datei:=20?= =?UTF-8?q?=20=20=20=20server/clients=5Fmanagement/mkclient.py=20=09neue?= =?UTF-8?q?=20Datei:=20=20=20=20=20server/clients=5Fmanagement/rmclient.py?= =?UTF-8?q?=20=09neue=20Datei:=20=20=20=20=20server/config/clients/fingerp?= =?UTF-8?q?rints=20=09neue=20Datei:=20=20=20=20=20server/main.py=20=09neue?= =?UTF-8?q?=20Datei:=20=20=20=20=20server/sec/ca/certs/ca.cert.pem=20=09ne?= =?UTF-8?q?ue=20Datei:=20=20=20=20=20server/sec/ca/private/ca.key.pem=20?= =?UTF-8?q?=09neue=20Datei:=20=20=20=20=20server/sec/server.crt.pem=20=09n?= =?UTF-8?q?eue=20Datei:=20=20=20=20=20server/sec/server.csr.pem=20=09neue?= =?UTF-8?q?=20Datei:=20=20=20=20=20server/sec/server.key.pem=20=09gel?= =?UTF-8?q?=C3=B6scht:=20=20=20=20=20=20=20main.py=20=09gel=C3=B6scht:=20?= =?UTF-8?q?=20=20=20=20=20=20sec/cert.pem=20=09gel=C3=B6scht:=20=20=20=20?= =?UTF-8?q?=20=20=20sec/key.pem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 ++++- client/client.py | 93 +++++++++++++++ client/sec/client.crt.pem | 26 +++++ client/sec/client.csr.pem | 17 +++ client/sec/client.key.pem | 28 +++++ container/Dockerfile | 14 +++ container/requirements.txt | 1 + lib/__pycache__/crypto_utils.cpython-313.pyc | Bin 0 -> 2017 bytes lib/__pycache__/jebp_utils.cpython-313.pyc | Bin 0 -> 3945 bytes .../terminal_table.cpython-313.pyc | Bin 0 -> 5051 bytes lib/crypto_utils.py | 32 ++++++ lib/jebp_utils.py | 47 ++++++++ lib/terminal_table.py | 106 ++++++++++++++++++ server/clients_management/chclient.py | 19 ++++ server/clients_management/lsclients.py | 14 +++ server/clients_management/mkclient.py | 22 ++++ server/clients_management/rmclient.py | 19 ++++ server/config/clients/fingerprints | Bin 0 -> 12288 bytes server/main.py | 91 +++++++++++++++ server/sec/ca/certs/ca.cert.pem | 23 ++++ server/sec/ca/private/ca.key.pem | 28 +++++ server/sec/server.crt.pem | 26 +++++ server/sec/server.csr.pem | 16 +++ server/sec/server.key.pem | 28 +++++ 24 files changed, 676 insertions(+), 1 deletion(-) create mode 100644 client/client.py create mode 100644 client/sec/client.crt.pem create mode 100644 client/sec/client.csr.pem create mode 100644 client/sec/client.key.pem create mode 100644 container/Dockerfile create mode 100644 container/requirements.txt create mode 100644 lib/__pycache__/crypto_utils.cpython-313.pyc create mode 100644 lib/__pycache__/jebp_utils.cpython-313.pyc create mode 100644 lib/__pycache__/terminal_table.cpython-313.pyc create mode 100644 lib/crypto_utils.py create mode 100644 lib/jebp_utils.py create mode 100644 lib/terminal_table.py create mode 100755 server/clients_management/chclient.py create mode 100755 server/clients_management/lsclients.py create mode 100755 server/clients_management/mkclient.py create mode 100755 server/clients_management/rmclient.py create mode 100644 server/config/clients/fingerprints create mode 100644 server/main.py create mode 100644 server/sec/ca/certs/ca.cert.pem create mode 100644 server/sec/ca/private/ca.key.pem create mode 100644 server/sec/server.crt.pem create mode 100644 server/sec/server.csr.pem create mode 100644 server/sec/server.key.pem diff --git a/README.md b/README.md index 69ca206..fcc0d70 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,28 @@ # jeb -jCloud Event Bus \ No newline at end of file +jCloud Event Bus + +## jebp (jCloud Event Bus Protocol) + +### Messages +All messages begin with `0x01`, followed by the content length length. The content length length is the length of the content length and representated by one byte. Then follows the content length and after that the message content. + +### Handshake +1. Server: Server protocol, version +2. Encryption + 1. Client: Serialized client public key + 2. Server: Serialized server public key + 3. Client: Client nonce + 4. Server: Server nonce + 5. Client: Random bytes (encrypted) + 6. Server: received bytes from client (not encrypted) + + The client closes the connection if the received bytes from server (7.) do not match the generated bytes (6.) to prevent malfunction and test the encryption. +3. Server authentication + 1. Server: Server certificate + + The client closes the connection if it does not trust the server certificate. +4. Client authentication + 1. Client: Client certificate + + The server closes the connection if the client is unauthorized. \ No newline at end of file diff --git a/client/client.py b/client/client.py new file mode 100644 index 0000000..a8214a3 --- /dev/null +++ b/client/client.py @@ -0,0 +1,93 @@ +import sys +import os +sys.path.append(os.getcwd()) +import asyncio +from lib.crypto_utils import ( + generate_keypair, + serialize_public_key, + deserialize_public_key, + derive_aes_key, +) +from lib.jebp_utils import sendmsg, readmsg, MessageFormatError, InvalidCertificateError, validate_cert +from cryptography.hazmat.primitives.ciphers.aead import AESGCM +from cryptography.hazmat.backends import default_backend +from cryptography import x509 +import base64 +import dbm + +HOST = "127.0.0.1" +PORT = 8888 +KNOWN_PROTOCOLS = (b'jebp 1.0',) +SERVER_CERT_FILE = 'server/sec/server.crt.pem' +CLIENT_CERT_FILE = 'client/sec/client.crt.pem' +REQUIRED_CERT_COMMON_NAME = 'jeb' +REQUIRED_ISSUER_CERT_COMMON_NAME = 'jCloudCA-Root-CA' + +async def main(): + reader, writer = await asyncio.open_connection(HOST, PORT) + + try: + try: + assert await readmsg(reader) in KNOWN_PROTOCOLS + except AssertionError: + print('Unknown protocol') + writer.close() + await writer.wait_closed() + return + + # BEGIN ENCRYPTION HANDSHAKE + + # 1. Client ECC keys + client_priv, client_pub = generate_keypair() + + # 2. Send client public key + await sendmsg(serialize_public_key(client_pub), writer) + + # 3. Receive server public key + server_pub_bytes = await readmsg(reader) + server_pub = deserialize_public_key(server_pub_bytes) + + # 4. Derive shared AES key + aes_key = derive_aes_key(client_priv, server_pub) + aesgcm = AESGCM(aes_key) + + client_nonce = os.urandom(12) + await sendmsg(client_nonce, writer) + + server_nonce = await readmsg(reader) + + test_bytes = os.urandom(32) + await sendmsg(test_bytes, writer, aesgcm, client_nonce) + rec = await readmsg(reader) + if rec != test_bytes: + raise Exception('encryption handshake failed') + await sendmsg(b'', writer) + + + # BEGIN SERVER AUTHENTICATION HANDSHAKE + + cert_data = await readmsg(reader, aesgcm, server_nonce) + cert = x509.load_der_x509_certificate(cert_data, default_backend()) + if not validate_cert(cert, REQUIRED_CERT_COMMON_NAME, REQUIRED_ISSUER_CERT_COMMON_NAME): + raise InvalidCertificateError('certificate not trusted') + + + # BEGIN CLIENT AUTHENTICATION HANDSHAKE + + with open(CLIENT_CERT_FILE, 'rb') as certfile: + cert_data = certfile.read() + certfile.close() + await sendmsg(base64.b64decode(cert_data.replace(b'-----BEGIN CERTIFICATE-----', b'').replace(b'-----END CERTIFICATE-----', b'').strip()), writer, aesgcm, client_nonce) + + + except MessageFormatError: + print('invalid message format') + except Exception as e: + print(f'{str(type(e))[8:-2]}: {e}') + finally: + writer.close() + await writer.wait_closed() + return + + +asyncio.run(main()) \ No newline at end of file diff --git a/client/sec/client.crt.pem b/client/sec/client.crt.pem new file mode 100644 index 0000000..e83a706 --- /dev/null +++ b/client/sec/client.crt.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEXjCCAkYCFDyqhr6D93HlZQSik8KgrPVXMHyiMA0GCSqGSIb3DQEBCwUAMHkx +CzAJBgNVBAYTAkRFMRwwGgYDVQQIDBNOb3JkcmhlaW4tV2VzdGZhbGVuMREwDwYD +VQQHDAhCb3JuaGVpbTERMA8GA1UECgwIakNsb3VkQ0ExCzAJBgNVBAsMAklUMRkw +FwYDVQQDDBBqQ2xvdWRDQS1Sb290LUNBMB4XDTI1MTIyOTIzMzcxNFoXDTI2MTIy +OTIzMzcxNFowXjELMAkGA1UEBhMCREUxHDAaBgNVBAgME05vcmRyaGVpbi1XZXN0 +ZmFsZW4xETAPBgNVBAcMCEJvcm5oZWltMQ8wDQYDVQQKDAZqQ2xvdWQxDTALBgNV +BAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbAEzcpkoL +DCIFUoa9JvOiIQJoe9j4cDsI65M1kczRzxGP5xJEtAJ6fz5gLIo+S4T35kXa/XzY +KeAQ2nYfaaF6BRDlRYtkwQUgpyMyN6h7nxlFbtF2iuogdLie1CXIAanWNFul1QF2 +Z2o74xh2KA0AiVuMk1Weg91TbKsT8loXkC9Xn6mqCwT43gf9JxAXFPLzvWARi9kQ +Srp+nkYr7sCRFbyGaA1KqZMJD0+rwWin4UxMkJtmM5FIPEgHI6iRhcXPtHiGQuFl +bKectfTBiEKb1g9DiY6bitcvseNse6v2XWp05pBp75ZOkygDyaF4Y2eKd/ixgcRv +NRJztk5cu0CDAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAExB5wKUbxvKSmqrwyT7 +ZmPDDXYAARIu4hGF1nDMlUdcbnUNncD1CMP0piF2B/VDoyFRAdwTTzROzmtBoyUl +tEy+d3YOJns5k+qgsMPFmbrEADipvDDOvXJ3/tgpN0tcXoaWBiDENdreH88olKwy +kDbC5fzve74DoQ/sC8ldTgNa/aZp8vILkt5j3UdK7IYSRLFhxyjjGoVxSb/y+Ja8 +98ImQknkv7laSrUNZFesC7DwT98S0G9OZZTt6Ksp4aZSTsr63ugC2Yo91Z9CC4z/ +qPP6v71YDHLtMzMbHahnlmV1AeMOVuIwioVBHg+ewjP0z71ixp/aTeJa7KyOZFii +fXSPy6WshdQFipr7v2g8eHTtwkyqehEhiSreeVfE1LZaCbyDfVXHaZeZUfrk4tRb +Eg50U0AMkdk5+JKRSetmMPpNjU56v8piKSHfoT5K2UGzOn9ymChkkNW8W4c0Q7CR +nJKp6Zzkd8RTJ45HUSbaTR5VVhV5VUHNuatNjsngezZtCqy95WxCJNzVy5m6Hx/c ++Lm5ku/6hVNpugLi5xuPwqjAKZzVgGAuV9le4TjVL+wn75Kx3KsfQvnoLySgsTtR +4Z4vdIbK4pxoKVdUvuyaUAopGr0ZHRX9LzMkWX/iswGB90KlTbUDzvw56mtoVPTI +v4Yoc/RsZTUA4j52LnO4vnW7 +-----END CERTIFICATE----- diff --git a/client/sec/client.csr.pem b/client/sec/client.csr.pem new file mode 100644 index 0000000..266b026 --- /dev/null +++ b/client/sec/client.csr.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICozCCAYsCAQAwXjELMAkGA1UEBhMCREUxHDAaBgNVBAgME05vcmRyaGVpbi1X +ZXN0ZmFsZW4xETAPBgNVBAcMCEJvcm5oZWltMQ8wDQYDVQQKDAZqQ2xvdWQxDTAL +BgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbAEzc +pkoLDCIFUoa9JvOiIQJoe9j4cDsI65M1kczRzxGP5xJEtAJ6fz5gLIo+S4T35kXa +/XzYKeAQ2nYfaaF6BRDlRYtkwQUgpyMyN6h7nxlFbtF2iuogdLie1CXIAanWNFul +1QF2Z2o74xh2KA0AiVuMk1Weg91TbKsT8loXkC9Xn6mqCwT43gf9JxAXFPLzvWAR +i9kQSrp+nkYr7sCRFbyGaA1KqZMJD0+rwWin4UxMkJtmM5FIPEgHI6iRhcXPtHiG +QuFlbKectfTBiEKb1g9DiY6bitcvseNse6v2XWp05pBp75ZOkygDyaF4Y2eKd/ix +gcRvNRJztk5cu0CDAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAmqHeGJsjdPXt +Bk2xQ8VNfgMKebULLmgB9WxP2agycRWS54guDUTD0dgl+ROZ2WKAKCI5fC0EAbd9 +7dzZX5RFRA0h0ZcbVsYhKtKVgL6rq1ujsVfL0YKrsQw/Uduz5Sqdo8IRvMbI748Z +tkiJO4rsvdXay7NMKAByMoKINXIYVj/gMpwyBsbuzxytLGODxCSP3bpCYwpL8DB9 +fGn1s/Dq8gVCIMJ2CBLjVhgBGNAb2eNhYBRysXCn8gP0S4DVW/9emi+Tu3Ckgk3C +siPSls2jmo+qM9kxaTZv684lsUIZlOjzYpRn/nsLpxB6O1VIBSM/fQ1bokKeLIVw +Pjd+o76eAw== +-----END CERTIFICATE REQUEST----- diff --git a/client/sec/client.key.pem b/client/sec/client.key.pem new file mode 100644 index 0000000..97bbdaa --- /dev/null +++ b/client/sec/client.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCbAEzcpkoLDCIF +Uoa9JvOiIQJoe9j4cDsI65M1kczRzxGP5xJEtAJ6fz5gLIo+S4T35kXa/XzYKeAQ +2nYfaaF6BRDlRYtkwQUgpyMyN6h7nxlFbtF2iuogdLie1CXIAanWNFul1QF2Z2o7 +4xh2KA0AiVuMk1Weg91TbKsT8loXkC9Xn6mqCwT43gf9JxAXFPLzvWARi9kQSrp+ +nkYr7sCRFbyGaA1KqZMJD0+rwWin4UxMkJtmM5FIPEgHI6iRhcXPtHiGQuFlbKec +tfTBiEKb1g9DiY6bitcvseNse6v2XWp05pBp75ZOkygDyaF4Y2eKd/ixgcRvNRJz +tk5cu0CDAgMBAAECggEAGHmlKWhWo8TwME/2N7MJYI3+z9nSinRKVh/AuIJDydN9 +LfBqWY/lHkuuYUhXMfwBzJU/bZN+XbHLEE42vcITve1D3DgtiSTTdvL9Y0YXpCGc +v3J+v82yp5pWtrnAF4NvuoO8/wQR/YzZ4Qf8ilfLqcyGuL1hFS00SyKLJxSrNjIO +zzGf8jXPIF7KzzWEWCqDJHCN4VctB0chX0FfhXOLGTDQse/ZdzBOdjWKKJaRLBjG +0g+sSmahZT5zJjAmj/6PjfYhic6ErVfDAwuzLJ9nMr8CoVPGTUaiNR9c93h/VBlC +M6+sp6FDdU6YCIu0rzoHbqtybNhAXIGd5CJrZM2f0QKBgQDSTtlyRT67UChw38s/ +lyNrkl5G3sIX969cW0O/eoH9iy5LNJolngXpOH3/KwSmOShizZmx+r7Qmp+idfPj +DTfe+Gnr6p89ekbpJ1i+l8DcjfUcIFMygrwD7fW3xeAQ/nJTkzozClSocn9wAsob +j55AP2Ocz1ziOmM692QQttoKEwKBgQC8rVUS/vugseBxuSys0r6l0k6zoJLNZFmy +gm138PZm9IpLg4nqUgSIfg3sJoZqkmVGfVtQbS1jjjYDDFY1QRai1gM63Ph3h6QL +/eMjFRJFtKsyiEsvlG/k3SL9x0zCIEnT+rLf7rdDovS55JSsqa7pAr+6b80bxwI+ +c2uYsLO90QKBgQDGaLdC1EszopMUsj3pN2imQweIqv3IaNdbNYr76dMbZaR+NRk8 +ZhJDjhVol6giPgh49mmK/PnqigYS2l8GFWFhjVE4zjf/Yw3lR0a1QSwlqBPXvjNf +kvFYb7aC8z7KKZOof1zH7HYkGSlbfnY4fE0bZfJPbV6+28DkT6NrFWctWwKBgQCa +QyGuoVl8flKiyKLVPo1vqG5+gQfl9Gk+AVOdYB8l+ERmD0sgkSRxsJaTgMAfvEgf +hPi23jzhC/HvNhP6AJiQVGhZpTdlCzq+LzuZgG3rHhdm/nZylWuS9JbaZSvGAH48 +WdoMKvId08tBfbltHmMK0huORECvuFuGUfoj4j1jMQKBgQDGxYkgzgNF1uzrr/lv +OuMZ06GO5PENAsh8WckiNFN7RAt5YS0alXbDazGInT/Qm5sKCZz8GHK7coes8diC +7U+9jlyXC6dMMuCRr8vOxJ4VmOz9suF0ZXQmVJTkjbTvM2UWiJE6Zo7C2UgNo+Cb +JEBKPquHhcULvqN9fYlMVbvguQ== +-----END PRIVATE KEY----- diff --git a/container/Dockerfile b/container/Dockerfile new file mode 100644 index 0000000..2564b5b --- /dev/null +++ b/container/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.13-slim + +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8888 + +CMD ["python3", "main.py"] \ No newline at end of file diff --git a/container/requirements.txt b/container/requirements.txt new file mode 100644 index 0000000..488c3aa --- /dev/null +++ b/container/requirements.txt @@ -0,0 +1 @@ +cryptography \ No newline at end of file diff --git a/lib/__pycache__/crypto_utils.cpython-313.pyc b/lib/__pycache__/crypto_utils.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..621c2a78c6353115abc321d37e299a232ca74dc7 GIT binary patch literal 2017 zcmbVM-A~(A6u)*Hlf(&WqLczdEMj!j0}?=`E$W0eHK`C$8H4 z(?lYMp#8IaPj^BH{YD4f#&wj_uR%FP5|Wq_T4e~6r8-+;S3@MU$`Q^Vhf3j9p75Y^ zqlgSh;Vq_osk_x#FY#N^>zp(&igq~?lOkJDn7PmsyrRCB;ICn&m_LeF1u_1TZYVMy zixrXeculr6Y~MtWo8X=3v<2c2ZT4(swx{-#iGF(* zjh+>u3AET5Ei!>(p!=vi(C5;%clzt>BznZGi5#Jq@G@0wU?SUiyFv68?f3A$7nO>4 z))y9Ul;^!zqgkox3Y~`M2z_1_Ju&MQ@|s!4`CWO>tjt=9hIKWs;#PiFshLeRUy+qP zY^ZtM!iH_-cX1_O(<^y}>^E$4yJ_n+E7#cfF7@sK2Q_4!kU`j;hu?Y+!~r@- zb6R{l@oeVt%#++vdg-8aEDX8BIcGR`G(7i0n0vv^oq{(a0tk;M*lrR-=^#T5C=9=k z{^y_^q778;w^9#io4rVsFTlJ^Z*{Kqt%#g6!LaNz{PlT>S%OH2>>98qf(^w~b))L> zJ0_{iw#a+&4(^qG8(V~4+lv&tZruH)8w*~1-4D%Oe~Fjc0BgNLuw`L&-P8?RWPBWg zIFSV1BGct3#Jd+)G6E)wm~{)p0Xh~gyTXJcOq`^~-PDwmnsQS!PHN^PIp!usCn>tg z+fMR!JB;G*v{5L|g9!5^Ne|#u*_K~JoFeEWu7UDjd@s|7o@;#>asl#Z8L)vCJ8_#t zghrvu@u6a^rZ;R|xzi*q9Dr5=mZx`!S>N`t$Le;2LPlt$0Jl--`f(dl@rB5ZgI}8Z zz6%0KDkNNC(h(+I;ieol7tdBp46UnDXo)LRzMN|tY`4k)bGfybS+HAC5SnbLmL~6Ek@KQ* zt!fh8*6JQ#!$#HCJkFA9w#VtljwucR+doxcK@|rTh%4SHtPmPmLaE~M(#nTWdOTi* zQrf~|h{Wl1vVk$_BQ2C#O(qaG3oDT476mYy%?30d4AyST*rJ7tOoMS4eya!q2%dS{ z&0KRb*Pf=IU3q-v$;k7MjxvRVd%p|Gll1WSv)|5smp@LXjuY^W{1FYu`M+amAo1DG z7oQyQZ?%PRc+6+}nwap!ppsRI+|c%On*0FLHwRU+uG?^XD<@m~^*XkRt_05=f3v@j zL6GiKZ}!aXsXI9hM1UPFmt+it7Y<4WO=?2<>A(IF@^2i#6M4UGs?8c+Ci7sY1jpe4h literal 0 HcmV?d00001 diff --git a/lib/__pycache__/jebp_utils.cpython-313.pyc b/lib/__pycache__/jebp_utils.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1cb593b511080e882f1c30d6942aca1b08c9bf5 GIT binary patch literal 3945 zcmdT{U2GHC6~5OV|HOYGKZX#q!6X6VLgKIrn~foXID`b)81Td-Y_d!z#~@2V*mxTwbQiIR#itOVt?3j#X(A>+ zmCdKjCmF)%GIPpu(n2gJt;7m_3$fLqM#Ng{5o;T_A2jFnkqUj(4x<&DMjdPo>lk-- zm{?~$nr5akaj}@il#PVdgEH9mUWMtIvCbdSnnyWuY~mtrFDi>SihyPe#^Ke z+Gxy`+D0^PjW(D1hIA0DK&b<>RpU%_PrkRA!`-V3x@!|SQ2U%tMgmx~_V%*_eM2BI zmk+jg1x%Vf&a0v-B}L6n`IVF`ZczPL*sQQ{BHE~h(Yt6I5zHcDVlgqZCc-4l0YPt16Ku=2TJPIN}7Y8}xcwI5?F`iot3AVrrsQ5vD{b9*m3g z!D(S8H5U&~@WMq=jt9kgQC5}Uv^Wu*ktS$6%gw3MjM6r{Kq^3y&gf+o2G~dF7c8kZ zhQGBTww;jY`57tRClXbflmswv-WvKz^zsqnqS~pLKc|LDUW3tfg^T>UDZhZuX;>Ca z&>BR;fU=oew|0`lG8qzP5rh+X5?mW^3@_ zo|~bxvp3E3>c-Kmmx!c_M6>duk`R)@=1-$ey#c?C5SXV4`M<$XV@1P;MiJt-z=!`= z6M0bhD4ngv*;XAZjAfEnb9gkN2xX3egd-S6o6$O-!(@pF0fSs*-^yOX`aLZ90^ot zjJhx@YGy?RTZ^)gii?^JTCy;!>R!~$;CRiVyF;_&@nDG)UXlY=4JXM?IF$H-X?Adx zoRS4mb3lX>RR@pe5mK@Wp<2L?VuhuCo$|P`DG<#`B|#`ss5KM?*mDXR8-V7kz_xNA#$$A5eku}EggZIAw z-ktiNiTBx+hV1Tl)6HGkhES#|^zcHa>eL_jOx5XC=FFO7%hLW^`){}9n7S-e_e!DJ z;d;0#?88rLaCkfOq}B?uW+fsY7l}Z#h(Q#MIs&g35eV1378iBc2W%t=b2_NWJ}T{}OTDiHxqxRvqmg_Em_n099@&MIXrpIRXukt9 zb#%#3fl+9$xodLnof-GedpPTE%-I_6O=oTG&piJ0uCA;nl(vV~8e4LW9ht_C&(3BW zJ99fa)6DiY->#gmHREgjt?)bfft>D+X4}}TZ#d^22H1OQa~^-j9`CSDwTQHktLf7{XdrFZyC`_Wg*F}UA# zZ}7HV<-H(1!r?CFG1>>><9#@MhDuzxu#mtJS+Y|~xU_erXHN_NOT1jW)psfKVFr+gYC{%YDy$S4N z^x@Dcsu!k4uP{d6?=mvLTZ7IqiYW*$UJV-bAe1^51zt{POhfRw`9BZem&#RBdZQR5zE3u#HPeaYB8r*&@cV*aCZU`5RZ2QtBdRp-E3W$n5d zxdt%^YrdVI9{%KT&exOi^?ac|y88Lm+>w#Yk&#v3Xtv_~Q{U(verfLy_hx*fi{W&| z`DZrA&9`q{x>I%Idvv$0I&0feP;=;$Lpfh}#@GEt{L#$kGr68~nVxg2zL9Lj=u_W_ zp=N}t87)_{J!@;CYMhm?zV;%=`}oz@4&*qG6&eGNecrx(%#&_!Upo`P#{*u?#w!c5 zAf>c@dV)+P2tPZu&^E--J9MWJ`gjsr z256okc7S7td!xf#`229cR?&Z15NA~>CFd)6y?Um3I$w#nSQRpAazw8T^?E@o3`O-P zTF$bvx?ECvZA;K0P)60@?8P<&oT52QqIR zSf`zb@4TeVdNm5;<2W5Xmll;5$d291dvo<|nfkVM+IeVwNt^Xrs3qRVxw9WUJ^MlW h!es8kbmqcz_Uy$M$c?uwGr78!OkK;rXh$!W{|&puFoFO8 literal 0 HcmV?d00001 diff --git a/lib/__pycache__/terminal_table.cpython-313.pyc b/lib/__pycache__/terminal_table.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b4fa015d0ae1ff268300c28231a2fa5c01d66a9 GIT binary patch literal 5051 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 bytes: + return public_key.public_bytes( + encoding=serialization.Encoding.X962, + format=serialization.PublicFormat.UncompressedPoint, + ) + + +def deserialize_public_key(data: bytes) -> ec.EllipticCurvePublicKey: + return ec.EllipticCurvePublicKey.from_encoded_point( + ec.SECP256R1(), data + ) + + +def derive_aes_key(private_key, peer_public_key) -> bytes: + shared_secret = private_key.exchange(ec.ECDH(), peer_public_key) + + return HKDF( + algorithm=hashes.SHA256(), + length=32, + salt=None, + info=b'handshake', + ).derive(shared_secret) diff --git a/lib/jebp_utils.py b/lib/jebp_utils.py new file mode 100644 index 0000000..ae5146d --- /dev/null +++ b/lib/jebp_utils.py @@ -0,0 +1,47 @@ +import asyncio +from cryptography.hazmat.primitives.ciphers.aead import AESGCM +from cryptography import x509 +from datetime import datetime, timezone + +MSG_START_BYTE = b'\x01' + +class MessageFormatError(Exception): ... +class InvalidCertificateError(Exception): ... + +def int_to_bytes(n: int, signed = False): + n = int(n) + return n.to_bytes((n.bit_length() + 7) // 8, signed = signed) + +async def sendmsg(m, writer: asyncio.StreamWriter, aesgcm: AESGCM = None, aesnonce = None, start_byte = MSG_START_BYTE): + if type(m) == str: + m = m.encode() + if aesgcm: + m = aesgcm.encrypt(aesnonce, m, None) + content_length = int_to_bytes(len(m)) + writer.write(start_byte + bytes([len(content_length)]) + content_length + m) + await writer.drain() + +async def readmsg(reader: asyncio.StreamReader, aesgcm: AESGCM = None, aesnonce = None, start_byte = MSG_START_BYTE): + if await reader.readexactly(1) != start_byte: + raise MessageFormatError('invalid message format') + content_length_length = await reader.readexactly(1) + content_length = await reader.readexactly(int.from_bytes(content_length_length)) + m = await reader.readexactly(int.from_bytes(content_length)) + if aesgcm: + return aesgcm.decrypt(aesnonce, m, None) + return m + +def validate_cert(cert: x509.Certificate, common_name, issuer_common_name, now = datetime.now(timezone.utc)): + if not cert.not_valid_before_utc <= now <= cert.not_valid_after_utc: + return False + cn = [a.value for a in cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)] + if len(cn) < 1: + return False + if common_name not in cn: + return False + cn = [a.value for a in cert.issuer.get_attributes_for_oid(x509.NameOID.COMMON_NAME)] + if len(cn) < 1: + return False + if issuer_common_name not in cn: + return False + return True \ No newline at end of file diff --git a/lib/terminal_table.py b/lib/terminal_table.py new file mode 100644 index 0000000..635c5ef --- /dev/null +++ b/lib/terminal_table.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 + +class TableBorderCharset: + def __init__(self, + corner_top_left, corner_top_right, corner_bottom_left, corner_bottom_right, + line_horizontal, line_vertical, + t_junction_horizontal_top, t_junction_horizontal_bottom, t_junction_vertical_left, t_junction_vertical_right, + intersection + ): + self.corner_top_left = corner_top_left + self.corner_top_right = corner_top_right + self.corner_bottom_left = corner_bottom_left + self.corner_bottom_right = corner_bottom_right + self.line_horizontal = line_horizontal + self.line_vertical = line_vertical + self.t_junction_horizontal_top = t_junction_horizontal_top + self.t_junction_horizontal_bottom = t_junction_horizontal_bottom + self.t_junction_vertical_left = t_junction_vertical_left + self.t_junction_vertical_right = t_junction_vertical_right + self.intersection = intersection + + + +BORDER_THIN = TableBorderCharset( + '┌', '┐', '└', '┘', + '─', '│', + '┴', '┬', '┤', '├', + '┼' +) + +BORDER_THICK = TableBorderCharset( + '┏', '┓', '┗', '┛', + '━', '┃', + '┻', '┳', '┫', '┣', + '╋' +) + +BORDER_DOUBLE = TableBorderCharset( + '╔', '╗', '╚', '╝', + '═', '║', + '╩', '╦', '╣', '╠', + '╬' +) + +BORDER_ROUND = TableBorderCharset( + '╭', '╮', '╰', '╯', + '─', '│', + '┴', '┬', '┤', '├', + '┼' +) + +def get_max_widths(data): + max_widths = {} + for row in data: + for col in row: + max_widths[col] = max(len(str(row[col])), max_widths.get(col, 0)) + return max_widths + + +def ascii_table(data, *, borders = False, border_charset = BORDER_THIN, column_space = 1, uppercase_column_headers = True): + column_widths = get_max_widths(data) + columns = list(column_widths.keys()) + table = f'''{(column_space * ' ').join([(c.upper() if uppercase_column_headers else c) + ' ' * (column_widths[c] - len(c)) for c in columns])}''' + table = '' + for col in columns: + column_widths[col] = max(column_widths[col], len(col)) + for col in columns: + table += col.upper() if uppercase_column_headers else col + table += ' ' * (column_widths[col] - len(col)) + table += ' ' * column_space + if borders: + table += border_charset.line_vertical + ' ' + for row in data: + table += '\n' + for column in columns: + value = str(row.get(column, '')) + table += value + table += ' ' * (column_widths[column] - len(str(value))) + table += column_space * ' ' + if borders: + table += border_charset.line_vertical + ' ' + if borders: + table_without_borders = table + table_width = max([len(r) for r in table_without_borders.split('\n')]) + table = border_charset.corner_top_left + for col in columns: + table += border_charset.line_horizontal * (column_widths[col] + 2) + table += border_charset.t_junction_horizontal_bottom + table = table[:-1] + border_charset.corner_top_right + table += '\n' + for row in table_without_borders.split('\n'): + table += border_charset.line_vertical + ' ' + table += row + table += '\n' + table += border_charset.t_junction_vertical_right + for i in range(len(columns)): + table += border_charset.line_horizontal * (column_widths[columns[i]] + 2) + table += border_charset.intersection if i != (len(columns) - 1) else border_charset.t_junction_vertical_left + table += '\n' + table = '\n'.join(table.split('\n')[:-2]) + '\n' + table += border_charset.corner_bottom_left + for col in columns: + table += border_charset.line_horizontal * (column_widths[col] + 2) + table += border_charset.t_junction_horizontal_top + table = table[:-1] + border_charset.corner_bottom_right + return table \ No newline at end of file diff --git a/server/clients_management/chclient.py b/server/clients_management/chclient.py new file mode 100755 index 0000000..7625b64 --- /dev/null +++ b/server/clients_management/chclient.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +import dbm +import sys +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +import hashlib + +if len(sys.argv) < 2: + print(f'{sys.argv[0]}: missing key hash') + sys.exit(1) + +with dbm.open('server/config/clients/fingerprints', 'c') as db: + if bytes.fromhex(sys.argv[1]) not in db.keys(): + print(f'{sys.argv[0]}: hash not registered') + sys.exit(1) + db[bytes.fromhex(sys.argv[1])] = input('New common name: ') + db.close() \ No newline at end of file diff --git a/server/clients_management/lsclients.py b/server/clients_management/lsclients.py new file mode 100755 index 0000000..0809af8 --- /dev/null +++ b/server/clients_management/lsclients.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import sys +import os +sys.path.append(os.getcwd()) +import dbm +from lib.terminal_table import ascii_table + +with dbm.open('server/config/clients/fingerprints', 'c') as db: + print(ascii_table([{ + 'Key Hash': k.hex(), + 'Common name': v.decode() + } for k, v in db.items()], )) + db.close() \ No newline at end of file diff --git a/server/clients_management/mkclient.py b/server/clients_management/mkclient.py new file mode 100755 index 0000000..cd61807 --- /dev/null +++ b/server/clients_management/mkclient.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +import dbm +import sys +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +import hashlib + +if len(sys.argv) < 2: + print(f'{sys.argv[0]}: missing common name') + sys.exit(1) + +try: + cert = x509.load_pem_x509_certificate(sys.stdin.buffer.read(), default_backend()) +except: + print(f'{sys.argv[0]}: invalid certificate') + sys.exit(1) + +with dbm.open('server/config/clients/fingerprints', 'c') as db: + db[hashlib.sha256(cert.public_key().public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo)).digest()] = sys.argv[1] + db.close() \ No newline at end of file diff --git a/server/clients_management/rmclient.py b/server/clients_management/rmclient.py new file mode 100755 index 0000000..2f08a2d --- /dev/null +++ b/server/clients_management/rmclient.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +import sys +import os +sys.path.append(os.getcwd()) +import dbm +from lib.terminal_table import ascii_table + +if len(sys.argv) < 2: + print(f'{sys.argv[0]}: missing key hash') + sys.exit(1) + +with dbm.open('server/config/clients/fingerprints', 'c') as db: + try: + del db[bytes.fromhex(sys.argv[1])] + except: + print(f'{sys.argv[0]}: hash not registered') + sys.exit(1) + db.close() \ No newline at end of file diff --git a/server/config/clients/fingerprints b/server/config/clients/fingerprints new file mode 100644 index 0000000000000000000000000000000000000000..9a3a8d2232ce425985f01919f62c4399d4a763a2 GIT binary patch literal 12288 zcmeI$F-rq66bJAmZB+^-f|f20L6j;=Ra^uYd-et)v{bLI$5kVio|f92sGEz6s~|Wy zIOtap{0=&agM%NyFW@95m78xq_&@S;_wq>Kep_y*DLYz;gWkzepv5W~CybE|AqXMN zWX5D1)VyTl!aQxA6pU0Z`UU<&81E9^Ih= z8ibMh@5c?ZK3`@drS&2uo#TCVHt2`uZtn+??hR|-p6;*M+1~5NtY^U_E|(Ys