Skip to content

Commit

Permalink
clean code
Browse files Browse the repository at this point in the history
  • Loading branch information
qwj committed Feb 14, 2021
1 parent d8b1287 commit b57d02f
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 60 deletions.
9 changes: 5 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ QuickStart
.. code:: rst
$ pip3 install pvpn
Successfully installed pvpn-0.2.0
Successfully installed pvpn-0.2.1
$ pvpn -p yourpassword
Serving on UDP :500 :4500...
^C
$ pvpn -wg 9000
Serving on UDP :500 :4500...
Serving on UDP :9000 WIREGUARD...
Serving on UDP :9000 (WIREGUARD)...
^C
Open server's UDP port :500 :4500 to your device. In device's system setting, add an "IPSec" (iOS) or "IPSec IKE PSK" (Android) node, write down the server address and password "yourpassword". Connect.

You should change the default password "test" to keep higher security. See "pvpn -h" for more options.
You should modify the default password "test" with a good one. See "pvpn -h" for more options.

Features
--------
Expand All @@ -56,11 +56,12 @@ Protocols
+-------------------+----------------+-------------------+----------------+------------------+
| IKEv2 PSK ✔ | IKEv2 **[2]** | "IPSec IKEv2 PSK" | IKEv2 | IKEv2 |
+-------------------+----------------+-------------------+----------------+------------------+
| WireGuard ✔ | WireGuard App |
| WireGuard ✔ | WireGuard App **[3]** |
+-------------------+----------------+-------------------+----------------+------------------+

| **[1]** Do not use certificates
| **[2]** Turn off "user authentication"
| **[3]** Turn off "preshared key"
Specifications
--------------
Expand Down
2 changes: 1 addition & 1 deletion pvpn/__doc__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__title__ = "pvpn"
__version__ = "0.2.0"
__version__ = "0.2.1"
__license__ = "MIT"
__description__ = "Python VPN server."
__keywords__ = "vpn ike esp ip tcp udp wireguard"
Expand Down
35 changes: 34 additions & 1 deletion pvpn/crypto.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import hashlib, os, random, hmac
from Crypto.Cipher import AES
from Crypto.Cipher import AES, ChaCha20_Poly1305
from . import enums

class Prf:
Expand Down Expand Up @@ -178,3 +178,36 @@ def DiffieHellman(group, peer):
else:
return pow(g, a, p).to_bytes(l, 'big'), pow(int.from_bytes(peer, 'big'), a, p).to_bytes(l, 'big')

def X25519(k, u):
p, a24 = 2**255-19, 121665
u, k = int.from_bytes(u, 'little') if isinstance(u, bytes) else u, int.from_bytes(k, 'little')
x_2, z_2, x_3, z_3, swap = 1, 0, u, 1, 0
k &= (1 << 256) - (1 << 255) - 8
k |= 1 << 254
for t in range(254, -1, -1):
k_t = (k >> t) & 1
if swap^k_t:
x_2, x_3, z_2, z_3 = x_3, x_2, z_3, z_2
swap = k_t
A, B, C, D = x_2+z_2, x_2-z_2, x_3+z_3, x_3-z_3
AA, BB, DA, CB = A*A, B*B, D*A, C*B
E = AA - BB
x_3 = pow(DA + CB, 2, p)
z_3 = u * pow(DA - CB, 2, p) % p
x_2 = AA * BB % p
z_2 = E * (AA + a24*E) % p
if swap:
x_2, x_3, z_2, z_3 = x_3, x_2, z_3, z_2
return (x_2 * pow(z_2, p-2, p) % p).to_bytes(32, 'little')

def aead_chacha20poly1305_encrypt(key, counter, plain_text, auth_text):
cipher = ChaCha20_Poly1305.new(key=key, nonce=b'\x00\x00\x00\x00'+counter.to_bytes(8, 'little'))
cipher.update(auth_text)
cipher_text, digest = cipher.encrypt_and_digest(plain_text)
return cipher_text+digest

def aead_chacha20poly1305_decrypt(key, counter, cipher_text, auth_text):
cipher = ChaCha20_Poly1305.new(key=key, nonce=b'\x00\x00\x00\x00'+counter.to_bytes(8, 'little'))
cipher.update(auth_text)
return cipher.decrypt_and_verify(cipher_text[:-16], cipher_text[-16:])

85 changes: 32 additions & 53 deletions pvpn/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,40 +436,27 @@ def reply(data):
else:
print('unknown packet', data, addr)



class WIREGUARD(asyncio.DatagramProtocol):
def __init__(self, args, sessions):
from nacl.public import PrivateKey
def __init__(self, args):
self.args = args
self.sessions = sessions
self.preshared_key = b'\x00'*32
self.ippacket = ip.IPPacket(args)
self.private_key = hashlib.blake2s(args.passwd.encode()).digest()
self.public_key = bytes(PrivateKey(self.private_key).public_key)
self.responders = {}
self.public_key = crypto.X25519(self.private_key, 9)
self.keys = {}
self.index_generators = {}
self.sender_index_generator = itertools.count()
print('======== WIREGUARD SETTING ========')
print('PublicKey:', base64.b64encode(self.public_key).decode())
print('===================================')
def connection_made(self, transport):
self.transport = transport
def datagram_received(self, data, addr):
from nacl import bindings
from nacl.public import PrivateKey
HASH = lambda x: hashlib.blake2s(x).digest()
MAC = lambda key, x: hashlib.blake2s(x, key=key, digest_size=16).digest()
HMAC = lambda key, x: hmac.digest(key, x, hashlib.blake2s)
DH = lambda pri, pub: bindings.crypto_scalarmult(pri, pub)
AEAD = lambda key, counter, plain_text, auth_text: bindings.crypto_aead_chacha20poly1305_ietf_encrypt(plain_text, auth_text, b'\x00\x00\x00\x00'+counter.to_bytes(8, 'little'), key)
AEAD_DECRYPT = lambda key, counter, cipher_text, auth_text: bindings.crypto_aead_chacha20poly1305_ietf_decrypt(cipher_text, auth_text, b'\x00\x00\x00\x00'+counter.to_bytes(8, 'little'), key)

if len(data) < 32:
return
cmd = int.from_bytes(data[0:4], 'little')
if cmd == 1:
if len(data) != 148:
return
if cmd == 1 and len(data) == 148:
HASH = lambda x: hashlib.blake2s(x).digest()
MAC = lambda key, x: hashlib.blake2s(x, key=key, digest_size=16).digest()
HMAC = lambda key, x: hmac.digest(key, x, hashlib.blake2s)
p, mac1, mac2 = struct.unpack('<116s16s16s', data)
assert mac1 == MAC(HASH(b"mac1----" + self.public_key), p)
assert mac2 == b'\x00'*16
Expand All @@ -479,57 +466,49 @@ def datagram_received(self, data, addr):
chaining_key = HASH(b"Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s")
hash0 = HASH(HASH(HASH(chaining_key + b"WireGuard v1 zx2c4 [email protected]") + self.public_key) + unencrypted_ephemeral)
chaining_key = HMAC(HMAC(chaining_key, unencrypted_ephemeral), b"\x01")
temp = HMAC(chaining_key, DH(self.private_key, unencrypted_ephemeral))
temp = HMAC(chaining_key, crypto.X25519(self.private_key, unencrypted_ephemeral))
chaining_key = HMAC(temp, b"\x01")
key = HMAC(temp, chaining_key + b"\x02")
static_public = AEAD_DECRYPT(key, 0, encrypted_static, hash0)
static_public = crypto.aead_chacha20poly1305_decrypt(HMAC(temp, chaining_key + b"\x02"), 0, encrypted_static, hash0)
hash0 = HASH(hash0 + encrypted_static)
temp = HMAC(chaining_key, DH(self.private_key, static_public))
temp = HMAC(chaining_key, crypto.X25519(self.private_key, static_public))
chaining_key = HMAC(temp, b"\x01")
key = HMAC(temp, chaining_key + b"\x02")
decrypted_timestamp = AEAD_DECRYPT(key, 0, encrypted_timestamp, hash0)
print('timestamp', decrypted_timestamp)
timestamp = crypto.aead_chacha20poly1305_decrypt(HMAC(temp, chaining_key + b"\x02"), 0, encrypted_timestamp, hash0)
hash0 = HASH(hash0 + encrypted_timestamp)

ephemeral_private = os.urandom(32)
ephemeral_public = bytes(PrivateKey(ephemeral_private).public_key)
ephemeral_public = crypto.X25519(ephemeral_private, 9)
hash0 = HASH(hash0 + ephemeral_public)
chaining_key = HMAC(HMAC(chaining_key, ephemeral_public), b"\x01")
chaining_key = HMAC(HMAC(chaining_key, DH(ephemeral_private, unencrypted_ephemeral)), b"\x01")
chaining_key = HMAC(HMAC(chaining_key, DH(ephemeral_private, static_public)), b"\x01")
chaining_key = HMAC(HMAC(HMAC(HMAC(HMAC(HMAC(chaining_key, ephemeral_public), b"\x01"), crypto.X25519(ephemeral_private, unencrypted_ephemeral)), b"\x01"), crypto.X25519(ephemeral_private, static_public)), b"\x01")
temp = HMAC(chaining_key, self.preshared_key)
chaining_key = HMAC(temp, b"\x01")
temp2 = HMAC(temp, chaining_key + b"\x02")
key = HMAC(temp, temp2 + b"\x03")
hash0 = HASH(hash0 + temp2)
encrypted_nothing = AEAD(key, 0, b"", hash0)
hash0 = HASH(hash0 + encrypted_nothing)
encrypted_nothing = crypto.aead_chacha20poly1305_encrypt(key, 0, b"", hash0)
#hash0 = HASH(hash0 + encrypted_nothing)
msg = struct.pack('<III32s16s', 2, index, sender_index, ephemeral_public, encrypted_nothing)
msg = msg + MAC(HASH(b"mac1----" + static_public), msg) + b'\x00'*16
self.transport.sendto(msg, addr)
print('login', addr, sender_index)

temp1 = HMAC(chaining_key, b"")
temp2 = HMAC(temp1, b"\x01")
temp3 = HMAC(temp1, temp2 + b"\x02")
self.responders[index] = [sender_index, temp2, 0, temp3, 0, []]
packet = struct.pack('<III32s16s', 2, index, sender_index, ephemeral_public, encrypted_nothing)
mac1 = MAC(HASH(b"mac1----" + static_public), packet)
mac2 = b'\x00'*16
msg = packet + mac1 + mac2
print('login', addr, sender_index)
self.transport.sendto(msg, addr)
elif cmd == 4:
_, receiver_index, counter = struct.unpack('<IIQ', data[:16])
packet = data[16:]
sender_index, receiving_key, receiving_key_counter, sending_key, sending_key_counter, msg_queue = self.responders[receiver_index]
encapsulated_packet = AEAD_DECRYPT(receiving_key, counter, packet, b'')
self.keys[index] = [sender_index, temp2, temp3]
self.index_generators[index] = itertools.count()
elif cmd == 4 and len(data) >= 32:
_, index, counter = struct.unpack('<IIQ', data[:16])
sender_index, receiving_key, sending_key = self.keys[index]
packet = crypto.aead_chacha20poly1305_decrypt(receiving_key, counter, data[16:], b'')
def reply(data):
counter = self.responders[receiver_index][4]
self.responders[receiver_index][4] += 1
counter = next(self.index_generators[index])
data = data + b''*((-len(data))%16)
encrypted_encapsulated_packet = AEAD(sending_key, counter, data, b'')
msg = struct.pack('<IIQ', 4, sender_index, counter) + encrypted_encapsulated_packet
msg = crypto.aead_chacha20poly1305_encrypt(sending_key, counter, data, b'')
msg = struct.pack('<IIQ', 4, sender_index, counter) + msg
self.transport.sendto(msg, addr)
return True
if encapsulated_packet:
self.ippacket.handle_ipv4(addr[:2], encapsulated_packet, reply)
if packet:
self.ippacket.handle_ipv4(addr[:2], packet, reply)
else:
reply(b'')

Expand All @@ -553,7 +532,7 @@ def main():
transport2, _ = loop.run_until_complete(loop.create_datagram_endpoint(lambda: SPE_4500(args, sessions), ('0.0.0.0', 4500)))
print('Serving on UDP :500 :4500...')
if args.wireguard:
transport3, _ = loop.run_until_complete(loop.create_datagram_endpoint(lambda: WIREGUARD(args, sessions), ('0.0.0.0', args.wireguard)))
transport3, _ = loop.run_until_complete(loop.create_datagram_endpoint(lambda: WIREGUARD(args), ('0.0.0.0', args.wireguard)))
print(f'Serving on UDP :{args.wireguard} (WIREGUARD)...')
else:
transport3 = None
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def find_value(name):
],
install_requires = [
'pycryptodome >= 3.7.2',
'pynacl >= 1.4.0',
'pproxy >= 2.0.0',
],
entry_points = {
Expand Down

0 comments on commit b57d02f

Please sign in to comment.