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:
@@ -1,3 +1,28 @@
|
||||
# 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.
|
||||
@@ -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())
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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"]
|
||||
@@ -0,0 +1 @@
|
||||
cryptography
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
Executable
+19
@@ -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()
|
||||
Executable
+14
@@ -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()
|
||||
Executable
+22
@@ -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()
|
||||
Executable
+19
@@ -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.
@@ -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())
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
Reference in New Issue
Block a user