geändert: README.md

neue Datei:     client/client.py
	neue Datei:     client/sec/client.crt.pem
	neue Datei:     client/sec/client.csr.pem
	neue Datei:     client/sec/client.key.pem
	neue Datei:     container/Dockerfile
	neue Datei:     container/requirements.txt
	neue Datei:     lib/__pycache__/crypto_utils.cpython-313.pyc
	neue Datei:     lib/__pycache__/jebp_utils.cpython-313.pyc
	neue Datei:     lib/__pycache__/terminal_table.cpython-313.pyc
	neue Datei:     lib/crypto_utils.py
	neue Datei:     lib/jebp_utils.py
	neue Datei:     lib/terminal_table.py
	neue Datei:     server/clients_management/chclient.py
	neue Datei:     server/clients_management/lsclients.py
	neue Datei:     server/clients_management/mkclient.py
	neue Datei:     server/clients_management/rmclient.py
	neue Datei:     server/config/clients/fingerprints
	neue Datei:     server/main.py
	neue Datei:     server/sec/ca/certs/ca.cert.pem
	neue Datei:     server/sec/ca/private/ca.key.pem
	neue Datei:     server/sec/server.crt.pem
	neue Datei:     server/sec/server.csr.pem
	neue Datei:     server/sec/server.key.pem
	gelöscht:       main.py
	gelöscht:       sec/cert.pem
	gelöscht:       sec/key.pem
This commit is contained in:
2025-12-30 01:43:23 +01:00
parent f9e7d51015
commit 1ac4aefdc8
24 changed files with 676 additions and 1 deletions
+25
View File
@@ -1,3 +1,28 @@
# jeb # jeb
jCloud Event Bus 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.
+93
View File
@@ -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())
+26
View File
@@ -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-----
+17
View File
@@ -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-----
+28
View File
@@ -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-----
+14
View File
@@ -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"]
+1
View File
@@ -0,0 +1 @@
cryptography
Binary file not shown.
Binary file not shown.
Binary file not shown.
+32
View File
@@ -0,0 +1,32 @@
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
def generate_keypair():
private_key = ec.generate_private_key(ec.SECP256R1())
return private_key, private_key.public_key()
def serialize_public_key(public_key: ec.EllipticCurvePublicKey) -> 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)
+47
View File
@@ -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
+106
View File
@@ -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
+19
View File
@@ -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()
+14
View File
@@ -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()
+22
View File
@@ -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()
+19
View File
@@ -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()
Binary file not shown.
+91
View File
@@ -0,0 +1,91 @@
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, validate_cert, InvalidCertificateError
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography import x509
import base64
import dbm
import hashlib
HOST = "0.0.0.0"
PORT = 8888
VERSION = 'jebp 1.0'
CERT_FILE = 'server/sec/server.crt.pem'
CLIENT_CERT_ISSUER_NAME = 'jCloudCA-Root-CA'
async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
addr = writer.get_extra_info('peername')
print(f'Connected to {addr}')
await sendmsg(VERSION, writer)
# BEGIN ENCRYPTION HANDSHAKE
server_priv, server_pub = generate_keypair()
client_pub_bytes = await readmsg(reader)
client_pub = deserialize_public_key(client_pub_bytes)
await sendmsg(serialize_public_key(server_pub), writer)
aes_key = derive_aes_key(server_priv, client_pub)
aesgcm = AESGCM(aes_key)
client_nonce = await readmsg(reader)
server_nonce = os.urandom(12)
await sendmsg(server_nonce, writer)
await sendmsg(await readmsg(reader, aesgcm, client_nonce), writer)
# BEGIN SERVER AUTHENTICATION HANDSHAKE
await readmsg(reader)
with open(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, server_nonce)
# BEGIN CLIENT AUTHENTICATION HANDSHAKE
cert = x509.load_der_x509_certificate(await readmsg(reader, aesgcm, client_nonce))
key_hash = hashlib.sha256(cert.public_key().public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo)).digest()
with dbm.open('server/config/clients/fingerprints', 'c') as db:
if key_hash not in db.keys():
raise InvalidCertificateError('client not known')
if not validate_cert(cert, db[key_hash].decode(), CLIENT_CERT_ISSUER_NAME):
raise InvalidCertificateError('client certificate not trusted')
print('Client authenticated')
writer.close()
await writer.wait_closed()
async def main():
server = await asyncio.start_server(handle_client, HOST, PORT)
addr = ', '.join(str(sock.getsockname()) for sock in server.sockets)
print(f'Listening on {addr}')
async with server:
await server.serve_forever()
if __name__ == '__main__':
asyncio.run(main())
+23
View File
@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDzTCCArWgAwIBAgIUOKOEzdpgmyw7M7cqcJvOqcSBiMEwDQYJKoZIhvcNAQEL
BQAwdjELMAkGA1UEBhMCREUxHDAaBgNVBAgME05vcmRyaGVpbi1XZXN0ZmFsZW4x
ETAPBgNVBAcMCEJvcm5oZWltMQ8wDQYDVQQKDAZqQ2xvdWQxETAPBgNVBAsMCFNl
Y3VyaXR5MRIwEAYDVQQDDAlqQ2xvdWQgQ0EwHhcNMjUxMjI5MTMxOTU1WhcNMzUx
MjI3MTMxOTU1WjB2MQswCQYDVQQGEwJERTEcMBoGA1UECAwTTm9yZHJoZWluLVdl
c3RmYWxlbjERMA8GA1UEBwwIQm9ybmhlaW0xDzANBgNVBAoMBmpDbG91ZDERMA8G
A1UECwwIU2VjdXJpdHkxEjAQBgNVBAMMCWpDbG91ZCBDQTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBANm+Vys2wsAhkFkOCa85/92GEG9x3Y3CWIpBgbB6
SvLYk0B3Ed78NvSXpigwYgeATtsctM/pMbEGlh4vhCuRSeNaSkYozet6ZR3/qRhI
hWkULrJE5uLjwFoQkUA94IfF1hfQW5y4vrUnrfDEBoiDUP1oLN5xeNs9MpV4NJR9
og57k6jrMIKA7Ep1pnszSo5AKfo/xfaI0EmYk8M4xUI2vZUsnh4uZtTfrkoCVsZW
tk6rg82fWW/y7dEHP2oF8y61STY8jO3QCnyPFoDJNaGJagJx9pSCWypCN0+vwTU/
arfsjnjQQfvKZaCGS9+J27Yn7iIv4gPjZt8L9pMwMSrgC4kCAwEAAaNTMFEwHQYD
VR0OBBYEFIjS2hku4PJxxOyeaOgiVyN3rEwoMB8GA1UdIwQYMBaAFIjS2hku4PJx
xOyeaOgiVyN3rEwoMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
ACG9lGtheCu4k/J4QjpxcgeVtK7UDy6CM4JVIdNzQP5Q+7uzuNjjo6IpJIjRwjNq
VRsWMtDR2Hlt0cffj0J9V8LVpDz5nah2jvxjRFo1RiBMuEhC2IYMWKdOoIeivwim
XZeUD6SEiR3x2xsJ/ZBHnZeKF33zwecIMwtrTqwMpnH+gmVDgslkR0gu1Y+SEX7n
zbysPRq3yNLW7Tdc9SvfK+2k2JOfur4XjPlXl0LwkAd1yE/MxS4qpLZUAmU/YqQH
zSfuHpTTbe0DqjotyECRs1PaPJMZ3pRFJ6PL6VZE+Xan/0p05gmwv2nimVvtRooS
NgSIHDq1B1qxB3r+VqBEA3g=
-----END CERTIFICATE-----
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDZvlcrNsLAIZBZ
DgmvOf/dhhBvcd2NwliKQYGwekry2JNAdxHe/Db0l6YoMGIHgE7bHLTP6TGxBpYe
L4QrkUnjWkpGKM3remUd/6kYSIVpFC6yRObi48BaEJFAPeCHxdYX0FucuL61J63w
xAaIg1D9aCzecXjbPTKVeDSUfaIOe5Oo6zCCgOxKdaZ7M0qOQCn6P8X2iNBJmJPD
OMVCNr2VLJ4eLmbU365KAlbGVrZOq4PNn1lv8u3RBz9qBfMutUk2PIzt0Ap8jxaA
yTWhiWoCcfaUglsqQjdPr8E1P2q37I540EH7ymWghkvfidu2J+4iL+ID42bfC/aT
MDEq4AuJAgMBAAECggEAZdF1Jm5xa/Fl89a2HZuT80zs44gNr2upBmFyWQkegedX
GAZ1s10h/4boOhPByzsq5JfMTwp+44YubUgP2GWUdP00DRGolMvDe98gfYvv4n5/
BXplssQIHrVvjmhA1Yxju/gA1sym1MQMuLCZU32AQjbUAGJ0PqSjwjQW9ja3MGio
/if+UMeVhg+Wf1ZYq3Le8GjkYVToZxw9RTCtF4ERBm3pCa3Jpul81kVwIijpBGCk
p72P6nwgHYKWdiwnJksk7TTsodVZNePwX1cPpX0ZpU7sPJeq9i9M+f5d6Lrd1ddb
vS2XFDPb5n5URWdANTtRznEqnscos9jCv2nwC1EyrwKBgQDw9jQ0I3qnP7p6m8++
8DByLJ1B6LamuCvNaSVKOIhCW2QZF66HJtLXFX65A8W+gEnPtuoBP6rAYLGFYI+z
usiuxEUwkU4ERbMV5BSqjHJIfm3C6T+6SM2ucX+A5WzIxOxnPsER7L/aSXjM114v
38XWTYuh1tmLZ4pwVF8gwcQ3fwKBgQDnVS8U24Kzx7SFfIRcxsBcG5NDkQp69x7O
vDszGF2o/JokCQcmKE/p6j6FMxwUMYZhMEoYeJ+rAkU6hXlk/GgtF/7BS7AoGC8K
AN+sSZlWVQB3p5GbZdUXq4zhflmYzOCa768+nMkTitwaeCzk5IJFU4dZ1Z64/uSW
vxa0RLGA9wKBgHHuR1Kbr+Oiaz6Lq1eBY7aoR5ahPLNpgzR1Ua9dOXf+lD6VBu1a
Ovh9blR2ZyoiqEOxHcWHX+nkDiudM3TsjPStUnWbToMQnZNtKyRUkbibJrSw6nPe
9nlTqtC8KYpoKcrLTih2g2Pnt1lLP3ptWLJyKeJUnrmeNEPWcMAhPfBrAoGATTF6
tX4OXkl8rrJ/RKo51EKLYJeMpL3DtP7QzlsKHfK0yOOOVChNimPVGwOUEhT4n4PK
peHSffETAtU5Tf6AENQZ+Yx1KtvvDmp+OPKSTWGt9z+ecfmIIw7mwvQzl65+IJVs
p/sPigrjKZ4CdRrfcZzO5KZJAbhktPzs6Zimlr8CgYEAuhcaEE3LeInshaPY4T/p
gcek6bRaScpi7aY0OBH6YPmO55kU+8G+eGv0ykQpu1wHJHaJIUG+s5JqfboUYVsZ
nbfuEeQ0rdcbvvaygbL0+bnDecam0Sa5x9G4UmdW/DeXb4HufCamnrxh2e59CYjZ
tVtU2HF/tg2GyPcQ9sEBIwY=
-----END PRIVATE KEY-----
+26
View File
@@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIETTCCAjUCFDyqhr6D93HlZQSik8KgrPVXMHygMA0GCSqGSIb3DQEBCwUAMHkx
CzAJBgNVBAYTAkRFMRwwGgYDVQQIDBNOb3JkcmhlaW4tV2VzdGZhbGVuMREwDwYD
VQQHDAhCb3JuaGVpbTERMA8GA1UECgwIakNsb3VkQ0ExCzAJBgNVBAsMAklUMRkw
FwYDVQQDDBBqQ2xvdWRDQS1Sb290LUNBMB4XDTI1MTIyOTE3MDkxMVoXDTI3MTIy
OTE3MDkxMVowTTELMAkGA1UEBhMCREUxDDAKBgNVBAgMA05SVzERMA8GA1UEBwwI
Qm9ybmhlaW0xDzANBgNVBAoMBmpDbG91ZDEMMAoGA1UEAwwDamViMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7zhAY0ytYvzhwuCI0nC7A485Jo74H5tO
x7sZ8XLkNWr/efjZNkEb0zfzN0BDXgWa74qmjv6JcAU7Fdzc3DMfFiCLbmgrQ0ev
FoD5PHRoC2x65LMclJQG9mNk3gWhnyB9mQRlJysi7RNDKTUruUpGg/p2wiEmXRJ3
xfWZ3Jdp0nvxEYbXrVemHBTcXqEW1Klhy7Ycr4cX7IoqLTk4MIOJrD90KFCyKXMt
6EoHQhhX2IQnX0R1ok/Suxwx+NG7yeDTe5CasqgxTvzuZeT5x8Y3vbv5uIrE6FaA
0XzvvHTtshqCcQeg1guSdzoRlgyMvh8G9l93VDXYFN922Mec9HfJGwIDAQABMA0G
CSqGSIb3DQEBCwUAA4ICAQBOVnuexCELMTymgGtx0GG4ZV/xuyGNOI9fxX0n31C9
6gQ4Uwe5gqy0O9g1Qw/UrA3Yf4o6R1gzxGZdJGUEKm7R1M3GE2/tChTrlRFbmiA5
LXC0FOhdhU+S6XBpOoj87i2FLts+4si0+EqxOUvR03EkwZeKJaKttNnHkmuxSMkm
7EJdgqTu7ohcBHlW3Gyc0TGnVX1vMBSkXrLwrRON3Zopd6nlbN/bzEY53aTGlHiN
IWfk+dE4l8ZLnJcihvm6hf2wwVWNGxiJojk5LsNDyqwvY+1dA4xU0QuJYx1rzJHW
cNfiC/qA6u9ml4N7UXZZRL/BTr8RF9zXmtr5hzV4xHWY/GmJBybLLPVlB/3fAkCC
OpNlTlOzqdd0o4y5gjup2ekEi6DFX0Ef0TVLdyJ0+OPm7HrlC76f+kE67ouKQjLT
bbnQ9j6u3GzNfuywWKPSizV/0X8X/zD/ix/jKOpE+JqRApRHLkRf7QGGOVudFYZW
L+C0/7EQNaSPjA5iDFlWOy9j20ZF44uYsL8FYJ5tjs49E9QQ2SwKf55Bdr8Kpxnw
W4zV4S9l+ZXRVK20GuXnpBvMxRDj6ZMeByj0YPyej4gCxnz1PlzT52SwwnwIj3lk
csnIxvJv15ks0ws9IQqXAEmcgu4jU08yQBh0S4LMj4l8IoDJL2bt/OO5KT7LjyLj
aQ==
-----END CERTIFICATE-----
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICkjCCAXoCAQAwTTELMAkGA1UEBhMCREUxDDAKBgNVBAgMA05SVzERMA8GA1UE
BwwIQm9ybmhlaW0xDzANBgNVBAoMBmpDbG91ZDEMMAoGA1UEAwwDamViMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7zhAY0ytYvzhwuCI0nC7A485Jo74
H5tOx7sZ8XLkNWr/efjZNkEb0zfzN0BDXgWa74qmjv6JcAU7Fdzc3DMfFiCLbmgr
Q0evFoD5PHRoC2x65LMclJQG9mNk3gWhnyB9mQRlJysi7RNDKTUruUpGg/p2wiEm
XRJ3xfWZ3Jdp0nvxEYbXrVemHBTcXqEW1Klhy7Ycr4cX7IoqLTk4MIOJrD90KFCy
KXMt6EoHQhhX2IQnX0R1ok/Suxwx+NG7yeDTe5CasqgxTvzuZeT5x8Y3vbv5uIrE
6FaA0XzvvHTtshqCcQeg1guSdzoRlgyMvh8G9l93VDXYFN922Mec9HfJGwIDAQAB
oAAwDQYJKoZIhvcNAQELBQADggEBAHeuAv3bYqIf2GGHvm0ztgar+npTd4/vPZDm
pZBdo3EAEe+Z9wZJCzSfUfpssfrRk94RPdbO10mdwBGDzcQ+V7Urb8jdFoGt1LRD
P17SvWqfYmGAyML3aA+v1efBkgOIRYPROsoTncP3LrSjRMKKC0MkO2u/QbZMiNce
ZqnzMvb3RtQAnOxNsS12FGEBiBownQrZqQyq/uIsnAjnA6QhoZJOFiXvupz9MyJq
MxeRMw9pBz7p31vhnSaWbrb1hmvhmontdmuQ2aNBEZSSMp+rxKLn3lvAlxyy22Uq
bgahiF6qi5XItOexSkVCBhSC7UACXGZr/9wQacH9FgZEUDCNIYY=
-----END CERTIFICATE REQUEST-----
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDvOEBjTK1i/OHC
4IjScLsDjzkmjvgfm07HuxnxcuQ1av95+Nk2QRvTN/M3QENeBZrviqaO/olwBTsV
3NzcMx8WIItuaCtDR68WgPk8dGgLbHrksxyUlAb2Y2TeBaGfIH2ZBGUnKyLtE0Mp
NSu5SkaD+nbCISZdEnfF9Zncl2nSe/ERhtetV6YcFNxeoRbUqWHLthyvhxfsiiot
OTgwg4msP3QoULIpcy3oSgdCGFfYhCdfRHWiT9K7HDH40bvJ4NN7kJqyqDFO/O5l
5PnHxje9u/m4isToVoDRfO+8dO2yGoJxB6DWC5J3OhGWDIy+Hwb2X3dUNdgU33bY
x5z0d8kbAgMBAAECggEAcPQtqvWMtT3U1/CvijZCh8a75JIDZOEvjK2y7Ugjq3lv
UkeHQM/zdINpq0ADz0R0SQE97i0P9j3yDTuxaaQV3JvXWnWDYAxcBxM1HC+W5TX+
vTg5mYpf0z1RZmhgTUPJKlRh8uGyZyD+SFnb4GzK6Qx9wOJoO/A5b9atJPS1ufV0
RByegK6eQefif6oAThazxb68vMy1By5cFiAEscql0Y45tyzGB/NWYbq9AlIVkHSd
XW3z0Ro1cP76t5SrEDQNZ3udGkwZViAVZS++sOu6aXmKda9c3QzzAzu+//nucFYl
H7xvOZhoi2jyWZWwEwD5JncEtUv+MthqUYMRY3/gmQKBgQD5o3SLasvjHlxD1Vkl
W8fR2pUjaPCjQIuis9IZ8JSGKPzvA0WTnS6Qc0SnGvqrSZxsnNONPS3lnqlDg7MQ
VbiFPbH97MZe3/wuy/9oo+ZxMUi8v1X0WzA4w1iBWUh9egAOT9y4V6VXYfqeZEOE
dIGebeVixSD0aPqfthfDy5un7wKBgQD1UNQKWTh0pvSUAauMHSLmyzGuQQqSyRpS
yhYDgWr3qhu3/FRIIQaCQ5lAxhepJTgs1xysNdyjM2Tg4rSkFXM8L6YJ3cnmd+SE
fXyrasjbehwgcnpXruVGE5iWOzEmf5/n26FunHcsFgV8H+hW2Rrt609Xhi6+7v0x
qAxwCH6llQKBgQDQ344dNO+Bih6i9zkOxBuOH/kXVq+5uMDdEt5hq+Vp15PCnJcz
qpmSbY/szesdNIs4aYOssprbfISdo+1IZqDsgzGRh/J24ayMr012DWU8IoN8wg82
VLIlXHWKTN0Vd+XiE7pXV6ZVVfqvWq8PfbgSilsa6FvphIjm5yI9RfhoawKBgQCV
cSv7MecGiT6te2b58DX2ywn00YKTcRcsIBfRAuIbBfHk3z6owhWo/W063HJFhHr4
NgMtlZJXiVOG/BR3cOnGXHCVyhrY32jCnX95HZBGw9imm5aUG+NoET//JQgIH+9V
26I4M/EVVkT5HKb5YUDad2LaOs/3WLe9rFYHWeVtEQKBgG5xsN8GhCGDuWKqHkLe
hUNAGFgutDQzOM9Huk1YRk6DNog7GXRzJYUWM/XP/TqRSlGZu0I3/1VedkKwiKH3
VDY/IikRvsGC0pfjM/hnh7EVDEjlTClYh+6uZbY2tpcq+FuR1mr/2uRWiLUZIoWs
Cjt9QMUxvxXAnjvXacDAkgCj
-----END PRIVATE KEY-----