Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
bdabkowski committed Apr 1, 2015
0 parents commit 11d9698
Show file tree
Hide file tree
Showing 25 changed files with 741 additions and 0 deletions.
Empty file added __init__.py
Empty file.
Binary file added __init__.pyc
Binary file not shown.
311 changes: 311 additions & 0 deletions dnscrypt.py
@@ -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 added dnscrypt.pyc
Binary file not shown.
52 changes: 52 additions & 0 deletions ed25519py.py
@@ -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:]

14 changes: 14 additions & 0 deletions slownacl/README
@@ -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/
8 changes: 8 additions & 0 deletions slownacl/__init__.py
@@ -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 added slownacl/__init__.pyc
Binary file not shown.

0 comments on commit 11d9698

Please sign in to comment.