Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 11d9698
Showing
25 changed files
with
741 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,311 @@ | ||
import socket | ||
import struct | ||
import StringIO | ||
import time | ||
import datetime | ||
import os | ||
from slownacl import poly1305, xsalsa20poly1305 | ||
|
||
USE_LOCAL_LIBS = 1 | ||
|
||
# OCTET 1,2 ID | ||
# OCTET 3,4 QR(1 bit) + OPCODE(4 bit)+ AA(1 bit) + TC(1 bit) + RD(1 bit)+ RA(1 bit) + | ||
# Z(3 bit) + RCODE(4 bit) | ||
# OCTET 5,6 QDCOUNT | ||
# OCTET 7,8 ANCOUNT | ||
# OCTET 9,10 NSCOUNT | ||
# OCTET 11,12 ARCOUNT | ||
|
||
|
||
class DnsHeader: | ||
def __init__(self): | ||
self.id = 0x1234 | ||
self.bits = 0x0100 # recursion desired | ||
self.qdCount = 0 | ||
self.anCount = 0 | ||
self.nsCount = 0 | ||
self.arCount = 1 | ||
|
||
def toBinary(self): | ||
return struct.pack('!HHHHHH', | ||
self.id, | ||
self.bits, | ||
self.qdCount, | ||
self.anCount, | ||
self.nsCount, | ||
self.arCount) | ||
|
||
def fromBinary(self, bin): | ||
if bin.read: | ||
bin = bin.read(12) | ||
(self.id, | ||
self.bits, | ||
self.qdCount, | ||
self.anCount, | ||
self.nsCount, | ||
self.arCount) = struct.unpack('!HHHHHH', bin) | ||
return self | ||
|
||
def __repr__(self): | ||
return '<DnsHeader %d, %d questions, %d answers>' % (self.id, self.qdCount, self.anCount) | ||
|
||
|
||
class DnsResourceRecord: | ||
pass | ||
|
||
|
||
class DnsAnswer(DnsResourceRecord): | ||
pass | ||
|
||
|
||
class DnsQuestion: | ||
def __init__(self): | ||
self.labels = [] | ||
self.qtype = 1 # A-record | ||
self.qclass = 1 # the Internet | ||
|
||
def toBinary(self): | ||
bin = '' | ||
for label in self.labels: | ||
assert len(label) <= 63 | ||
bin += struct.pack('B', len(label)) | ||
bin += label | ||
bin += '\0' # Labels terminator | ||
bin += struct.pack('!HH', self.qtype, self.qclass) | ||
return bin | ||
|
||
|
||
class DnsPacket: | ||
def __init__(self, header=None): | ||
self.header = header | ||
self.questions = [] | ||
self.answers = [] | ||
|
||
def addQuestion(self, question): | ||
self.header.qdCount += 1 | ||
self.questions.append(question) | ||
|
||
def toBinary(self): | ||
bin = self.header.toBinary() | ||
for question in self.questions: | ||
bin += question.toBinary() | ||
return bin | ||
|
||
def __repr__(self): | ||
return '<DnsPacket %s>' % (self.header) | ||
|
||
|
||
class DnscryptException(Exception): | ||
pass | ||
|
||
|
||
class BinReader(StringIO.StringIO): | ||
def unpack(self, fmt): | ||
size = struct.calcsize(fmt) | ||
bin = self.read(size) | ||
return struct.unpack(fmt, bin) | ||
|
||
|
||
class DnsPacketConverter: | ||
def fromBinary(self, bin): | ||
reader = BinReader(bin) | ||
header = DnsHeader().fromBinary(reader) | ||
packet = DnsPacket(header) | ||
for qi in range(header.qdCount): | ||
q = self.readQuestion(reader) | ||
packet.questions.append(q) | ||
for ai in range(header.anCount): | ||
aa = self.readAnswer(reader) | ||
packet.answers.append(aa) | ||
return packet | ||
|
||
def readQuestion(self, reader): | ||
question = DnsQuestion() | ||
question.labels = self.readLabels(reader) | ||
(question.qtype, question.qclass) = reader.unpack('!HH') | ||
return question | ||
|
||
def readAnswer(self, reader): | ||
answer = DnsAnswer() | ||
answer.name = self.readLabels(reader) | ||
(type, rrclass, ttl, rdlength) = reader.unpack('!HHiH') | ||
answer.rdata = reader.read(rdlength) | ||
return answer.rdata | ||
|
||
def readLabels(self, reader): | ||
labels = [] | ||
while True: | ||
(length,) = reader.unpack('B') | ||
if length == 0: | ||
break | ||
|
||
# Compression | ||
compressionMask = 0b11000000 | ||
if length & compressionMask: | ||
byte1 = length & ~compressionMask | ||
(byte2,) = reader.unpack('B') | ||
offset = byte1 << 8 | byte2 | ||
oldPosition = reader.tell() | ||
result = self.readLabels(reader) | ||
reader.seek(oldPosition) | ||
return result | ||
|
||
label = reader.read(length) | ||
labels.append(label) | ||
return labels | ||
|
||
|
||
def get_public_key(ip, port, provider_key, provider_url): | ||
'''Get public key from provider.''' | ||
header = DnsHeader() | ||
|
||
question = DnsQuestion() | ||
question.labels = provider_url.split('.') | ||
question.qtype = 16 # TXT record | ||
|
||
packet = DnsPacket(header) | ||
packet.addQuestion(question) | ||
|
||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | ||
dest = (ip, port) | ||
sock.sendto(packet.toBinary(), dest) | ||
|
||
(response, address) = sock.recvfrom(1024) | ||
|
||
bincert = response.encode('hex')[-232:] | ||
|
||
cert_start = datetime.datetime.fromtimestamp(int(bincert[216:224], 16)) | ||
cert_end = datetime.datetime.fromtimestamp(int(bincert[224:], 16)) | ||
now = datetime.datetime.now() | ||
|
||
if now < cert_start or now > cert_end: | ||
raise DnscryptException("Certificate expired.") | ||
|
||
if USE_LOCAL_LIBS: | ||
try: | ||
import pysodium | ||
return pysodium.crypto_sign_open(bincert.decode('hex'), provider_key.decode('hex')) | ||
except ImportError: | ||
pass | ||
|
||
try: | ||
import nacl | ||
import nacl.bindings | ||
return nacl.bindings.crypto_sign_open(bincert.decode('hex'), provider_key.decode('hex')) | ||
except ImportError: | ||
pass | ||
|
||
import ed25519py | ||
return ed25519py.crypto_sign_open(bincert.decode('hex'), provider_key.decode('hex')) | ||
|
||
|
||
def generate_keypair(): | ||
if USE_LOCAL_LIBS: | ||
try: | ||
import nacl | ||
import nacl.bindings | ||
return nacl.bindings.crypto_box_keypair() | ||
except ImportError: | ||
pass | ||
return xsalsa20poly1305.box_curve25519xsalsa20poly1305_keypair() | ||
|
||
|
||
def create_nmkey(pk, sk): | ||
try: | ||
if USE_LOCAL_LIBS: | ||
try: | ||
import nacl | ||
import nacl.bindings | ||
return nacl.bindings.crypto_box_beforenm(pk, sk) | ||
except ImportError: | ||
pass | ||
return xsalsa20poly1305.box_curve25519xsalsa20poly1305_beforenm(pk, sk) | ||
except ValueError: | ||
raise DnscryptException("Invalid public key.") | ||
|
||
|
||
def encode_message(message, nonce, nmkey): | ||
try: | ||
if USE_LOCAL_LIBS: | ||
try: | ||
import nacl | ||
import nacl.bindings | ||
return nacl.bindings.crypto_box_afternm(message, nonce + 12 * '\x00', nmkey) | ||
except ImportError: | ||
pass | ||
return xsalsa20poly1305.box_curve25519xsalsa20poly1305_afternm(message, nonce + 12 * '\x00', nmkey) | ||
except ValueError: | ||
raise DnscryptException("Message encoding error.") | ||
|
||
|
||
def decode_message(answer, nonce, nmkey): | ||
try: | ||
if USE_LOCAL_LIBS: | ||
try: | ||
import nacl | ||
import nacl.bindings | ||
return nacl.bindings.crypto_box_open_afternm(answer, nonce, nmkey) | ||
except ImportError: | ||
pass | ||
return xsalsa20poly1305.box_curve25519xsalsa20poly1305_open_afternm(answer, nonce, nmkey) | ||
except ValueError: | ||
raise DnscryptException("Message decoding error.") | ||
|
||
|
||
def query(url, ip, port, provider_key, provider_url, record_type=1): | ||
# get public key from provider | ||
try: | ||
provider_pk = get_public_key(ip, port, provider_key, provider_url)[:32] | ||
except DnscryptException: | ||
raise DnscryptException("Certificate expired.") | ||
|
||
magic_query = '7PYqwfzt' | ||
|
||
# create dns query | ||
header = DnsHeader() | ||
|
||
question = DnsQuestion() | ||
question.labels = url.split('.') | ||
question.qtype = record_type | ||
|
||
packet = DnsPacket(header) | ||
packet.addQuestion(question) | ||
|
||
# generate a local keypair | ||
(pk, sk) = generate_keypair() | ||
|
||
# create nmkey out of provider's public key and local secret key | ||
nmkey = create_nmkey(provider_pk, sk) | ||
|
||
message = packet.toBinary() + '\x00\x00\x29\x04\xe4' + 6 * '\x00' + '\x80' + 404 * '\x00' | ||
|
||
nonce = "%x" % int(time.time()) + os.urandom(4) | ||
|
||
encoded_message = encode_message(message, nonce, nmkey) | ||
|
||
#poly = poly1305.onetimeauth_poly1305(encoded_message, provider_pk) not quite sure if that's needed for something... | ||
|
||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | ||
dest = (ip, port) | ||
|
||
sock.sendto(magic_query + pk + nonce + encoded_message, dest) | ||
(response, address) = sock.recvfrom(1024) | ||
|
||
resp_magic_query = response[:8] | ||
resp_client_nonce = response[8:20] | ||
resp_server_nonce = response[20:32] | ||
resp_answer = response[32:] | ||
|
||
if resp_magic_query != 'r6fnvWj8': | ||
raise DnscryptException("Invalid magic query received.") | ||
if resp_client_nonce != nonce: | ||
raise DnscryptException("Invalid nonce received.") | ||
|
||
decoded_answer = decode_message(resp_answer, resp_client_nonce + resp_server_nonce, nmkey) | ||
|
||
conv = DnsPacketConverter() | ||
packet = conv.fromBinary(decoded_answer) | ||
return packet | ||
|
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
import warnings | ||
import os | ||
|
||
from collections import namedtuple | ||
import djbec | ||
|
||
__all__ = ['crypto_sign', 'crypto_sign_open', 'crypto_sign_keypair', 'Keypair', | ||
'PUBLICKEYBYTES', 'SECRETKEYBYTES', 'SIGNATUREBYTES'] | ||
|
||
PUBLICKEYBYTES=32 | ||
SECRETKEYBYTES=64 | ||
SIGNATUREBYTES=64 | ||
|
||
Keypair = namedtuple('Keypair', ('vk', 'sk')) # verifying key, secret key | ||
|
||
def crypto_sign_keypair(seed=None): | ||
"""Return (verifying, secret) key from a given seed, or os.urandom(32)""" | ||
if seed is None: | ||
seed = os.urandom(PUBLICKEYBYTES) | ||
else: | ||
warnings.warn("ed25519ll should choose random seed.", | ||
RuntimeWarning) | ||
if len(seed) != 32: | ||
raise ValueError("seed must be 32 random bytes or None.") | ||
skbytes = seed | ||
vkbytes = djbec.publickey(skbytes) | ||
return Keypair(vkbytes, skbytes+vkbytes) | ||
|
||
|
||
def crypto_sign(msg, sk): | ||
"""Return signature+message given message and secret key. | ||
The signature is the first SIGNATUREBYTES bytes of the return value. | ||
A copy of msg is in the remainder.""" | ||
if len(sk) != SECRETKEYBYTES: | ||
raise ValueError("Bad signing key length %d" % len(sk)) | ||
vkbytes = sk[PUBLICKEYBYTES:] | ||
skbytes = sk[:PUBLICKEYBYTES] | ||
sig = djbec.signature(msg, skbytes, vkbytes) | ||
return sig + msg | ||
|
||
|
||
def crypto_sign_open(signed, vk): | ||
"""Return message given signature+message and the verifying key.""" | ||
if len(vk) != PUBLICKEYBYTES: | ||
raise ValueError("Bad verifying key length %d" % len(vk)) | ||
rc = djbec.checkvalid(signed[:SIGNATUREBYTES], signed[SIGNATUREBYTES:], vk) | ||
if not rc: | ||
raise ValueError("rc != True", rc) | ||
return signed[SIGNATUREBYTES:] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
slownacl | ||
Matthew Dempsky | ||
Public domain. | ||
|
||
slownacl is a high-level reference implementation of the Python NaCl | ||
API. It is intended to be readily understandable. | ||
|
||
WARNING: Because slownacl uses Python's native big number support, it | ||
cannot guarantee timing invariance. Its use should therefore be | ||
limited to situations where timing attacks are not an issue. | ||
|
||
For more information about the CACE NaCl project, see: | ||
|
||
http://nacl.cace-project.eu/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from util import xor, randombytes | ||
from verify import verify16, verify32 | ||
from salsa20 import core_hsalsa20, stream_salsa20, stream_salsa20_xor, stream_xsalsa20, stream_xsalsa20_xor | ||
from poly1305 import onetimeauth_poly1305, onetimeauth_poly1305_verify | ||
from sha512 import hash_sha512, auth_hmacsha512, auth_hmacsha512_verify | ||
from curve25519 import smult_curve25519, smult_curve25519_base | ||
from salsa20hmacsha512 import secretbox_salsa20hmacsha512, secretbox_salsa20hmacsha512_open, box_curve25519salsa20hmacsha512_keypair, box_curve25519salsa20hmacsha512, box_curve25519salsa20hmacsha512_open, box_curve25519salsa20hmacsha512_beforenm, box_curve25519salsa20hmacsha512_afternm, box_curve25519salsa20hmacsha512_open_afternm | ||
from xsalsa20poly1305 import secretbox_xsalsa20poly1305, secretbox_xsalsa20poly1305_open, box_curve25519xsalsa20poly1305_keypair, box_curve25519xsalsa20poly1305, box_curve25519xsalsa20poly1305_open, box_curve25519xsalsa20poly1305_beforenm, box_curve25519xsalsa20poly1305_afternm, box_curve25519xsalsa20poly1305_open_afternm |
Binary file not shown.
Oops, something went wrong.