dnscrypt

package module
v2.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2020 License: Unlicense Imports: 21 Imported by: 28

README

Build Status Code Coverage Go Report Card Go Doc

DNSCrypt Go

Golang-implementation of the DNSCrypt v2 protocol.

This repo includes everything you need to work with DNSCrypt. You can run your own resolver, make DNS lookups to other DNSCrypt resolvers, and you can use it as a library in your own projects.

Command-line tool

dnscrypt is a helper tool that can work as a DNSCrypt client or server.

Please note, that even though this tool can work as a server, it's purpose is merely testing. Use dnsproxy or AdGuard Home for real-life purposes.

How to install

Download and unpack an archive for your platform from the latest release.

Running a server

Generate a configuration file for running a DNSCrypt server:

./dnscrypt generate --provider-name=2.dnscrypt-cert.example.org --out=config.yaml

It will generate a configuration file that looks like this:

provider_name: 2.dnscrypt-cert.example.org
public_key: F11DDBCC4817E543845FDDD4CB881849B64226F3DE397625669D87B919BC4FB0
private_key: 5752095FFA56D963569951AFE70FE1690F378D13D8AD6F8054DFAA100907F8B6F11DDBCC4817E543845FDDD4CB881849B64226F3DE397625669D87B919BC4FB0
resolver_secret: 9E46E79FEB3AB3D45F4EB3EA957DEAF5D9639A0179F1850AFABA7E58F87C74C4
resolver_public: 9327C5E64783E19C339BD6B680A56DB85521CC6E4E0CA5DF5274E2D3CE026C6B
es_version: 1
certificate_ttl: 0s
  • provider_name - DNSCrypt resolver name.
  • public_key, private_key - keypair that is used by the DNSCrypt resolver to sign the certificate.
  • resolver_secret, resolver_public - keypair that is used by the DNSCrypt resolver to encrypt and decrypt messages.
  • es_version - crypto to use. Can be 1 (XSalsa20Poly1305) or 2 (XChacha20Poly1305).
  • certificate_ttl - certificate time-to-live. By default it's set to 0 and in this case 1-year cert is generated. The certificate is generated on dnscrypt start-up and it will only be valid for the specified amount of time. You should periodically restart dnscrypt to rotate the cert.

This configuration file can be used to run a DNSCrypt forwarding server:

./dnscrypt server --config=config.yaml --forward=94.140.14.140:53

Now you can go to https://dnscrypt.info/stamps and use provider_name and public_key from this configuration to generate a DNS stamp. Here's how it looks like for a server running on 127.0.0.1:443:

sdns://AQcAAAAAAAAADTEyNy4wLjAuMTo0NDMg8R3bzEgX5UOEX93Uy4gYSbZCJvPeOXYlZp2HuRm8T7AbMi5kbnNjcnlwdC1jZXJ0LmV4YW1wbGUub3Jn
Making lookups

You can use that stamp to send a DNSCrypt request to your server:

./dnscrypt lookup-stamp \
    --stamp=sdns://AQcAAAAAAAAADTEyNy4wLjAuMTo0NDMg8R3bzEgX5UOEX93Uy4gYSbZCJvPeOXYlZp2HuRm8T7AbMi5kbnNjcnlwdC1jZXJ0LmV4YW1wbGUub3Jn \
    --domain=example.org \
    --type=a

You can also send a DNSCrypt request using a command that does not require stamps:

./dnscrypt lookup \
    --provider-name=2.dnscrypt-cert.opendns.com \
    --public-key=b7351140206f225d3e2bd822d7fd691ea1c33cc8d6668d0cbe04bfabca43fb79 \
    --addr=208.67.220.220 \
    --domain=example.org \
    --type=a

Programming interface

Client
// AdGuard DNS stamp
stampStr := "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20"

// Initializing the DNSCrypt client
c := dnscrypt.Client{Net: "udp", Timeout: 10 * time.Second}

// Fetching and validating the server certificate
resolverInfo, err := client.Dial(stampStr)
if err != nil {
    return err
}

// Create a DNS request
req := dns.Msg{}
req.Id = dns.Id()
req.RecursionDesired = true
req.Question = []dns.Question{
    {Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
}

// Get the DNS response
reply, err := c.Exchange(&req, resolverInfo)

Server

// Prepare the test DNSCrypt server config
rc, err := dnscrypt.GenerateResolverConfig("example.org", nil)
if err != nil {
    return err
}

cert, err := rc.CreateCert()
if err != nil {
    return err
}

s := &dnscrypt.Server{
    ProviderName: rc.ProviderName,
    ResolverCert: cert,
    Handler:      dnscrypt.DefaultHandler,
}

// Prepare TCP listener
tcpConn, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4zero, Port: 443})
if err != nil {
    return err
}

// Prepare UDP listener
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 443})
if err != nil {
    return err
}

// Start the server
go s.ServeUDP(udpConn)
go s.ServeTCP(tcpConn)

Documentation

Overview

Package dnscrypt includes everything you need to work with DNSCrypt. You can run your own resolver, make DNS lookups to other DNSCrypt resolvers, and you can use it as a library in your own projects.

Here's how to create a simple DNSCrypt client:

// AdGuard DNS stamp
stampStr := "sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20"

// Initializing the DNSCrypt client
c := dnscrypt.Client{Net: "udp", Timeout: 10 * time.Second}

// Fetching and validating the server certificate
resolverInfo, err := client.Dial(stampStr)
if err != nil {
	return err
}

// Create a DNS request
req := dns.Msg{}
req.Id = dns.Id()
req.RecursionDesired = true
req.Question = []dns.Question{
	{Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
}

// Get the DNS response
reply, err := c.Exchange(&req, resolverInfo)

Here's how to run a DNSCrypt resolver:

// Prepare the test DNSCrypt server config
rc, err := dnscrypt.GenerateResolverConfig("example.org", nil)
if err != nil {
	return err
}

cert, err := rc.CreateCert()
if err != nil {
	return err
}

s := &dnscrypt.Server{
	ProviderName: rc.ProviderName,
	ResolverCert: cert,
	Handler:      dnscrypt.DefaultHandler,
}

// Prepare TCP listener
tcpConn, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.IPv4zero, Port: 443})
if err != nil {
	return err
}

// Prepare UDP listener
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 443})
if err != nil {
	return err
}

// Start the server
go s.ServeUDP(udpConn)
go s.ServeTCP(tcpConn)

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrTooShort - DNS query is shorter than possible
	ErrTooShort = errors.New("DNSCrypt message is too short")

	// ErrQueryTooLarge - DNS query is larger than max allowed size
	ErrQueryTooLarge = errors.New("DNSCrypt query is too large")

	// ErrEsVersion - cert contains unsupported es-version
	ErrEsVersion = errors.New("unsupported es-version")

	// ErrInvalidDate - cert is not valid for the current time
	ErrInvalidDate = errors.New("cert has invalid ts-start or ts-end")

	// ErrInvalidCertSignature - cert has invalid signature
	ErrInvalidCertSignature = errors.New("cert has invalid signature")

	// ErrInvalidQuery - failed to decrypt a DNSCrypt query
	ErrInvalidQuery = errors.New("DNSCrypt query is invalid and cannot be decrypted")

	// ErrInvalidClientMagic - client-magic does not match
	ErrInvalidClientMagic = errors.New("DNSCrypt query contains invalid client magic")

	// ErrInvalidResolverMagic - server-magic does not match
	ErrInvalidResolverMagic = errors.New("DNSCrypt response contains invalid resolver magic")

	// ErrInvalidResponse - failed to decrypt a DNSCrypt response
	ErrInvalidResponse = errors.New("DNSCrypt response is invalid and cannot be decrypted")

	// ErrInvalidPadding - failed to unpad a query
	ErrInvalidPadding = errors.New("invalid padding")

	// ErrInvalidDNSStamp - invalid DNS stamp
	ErrInvalidDNSStamp = errors.New("invalid DNS stamp")

	// ErrFailedToFetchCert - failed to fetch DNSCrypt certificate
	ErrFailedToFetchCert = errors.New("failed to fetch DNSCrypt certificate")

	// ErrCertTooShort - failed to deserialize cert, too short
	ErrCertTooShort = errors.New("cert is too short")

	// ErrCertMagic - invalid cert magic
	ErrCertMagic = errors.New("invalid cert magic")

	// ErrServerConfig - failed to start the DNSCrypt server - invalid configuration
	ErrServerConfig = errors.New("invalid server configuration")
)

Functions

func HexDecodeKey

func HexDecodeKey(str string) ([]byte, error)

HexDecodeKey - decodes a hex-encoded string with (optional) colons to a byte array.

func HexEncodeKey

func HexEncodeKey(b []byte) string

HexEncodeKey - encodes a byte slice to a hex-encoded string.

Types

type Cert

type Cert struct {
	// Serial - a 4 byte serial number in big-endian format. If more than
	// one certificates are valid, the client must prefer the certificate
	// with a higher serial number.
	Serial uint32

	// <es-version> ::= the cryptographic construction to use with this
	// certificate.
	// For X25519-XSalsa20Poly1305, <es-version> must be 0x00 0x01.
	// For X25519-XChacha20Poly1305, <es-version> must be 0x00 0x02.
	EsVersion CryptoConstruction

	// Signature - a 64-byte signature of (<resolver-pk> <client-magic>
	// <serial> <ts-start> <ts-end> <extensions>) using the Ed25519 algorithm and the
	// provider secret key. Ed25519 must be used in this version of the
	// protocol.
	Signature [ed25519.SignatureSize]byte

	// ResolverPk - the resolver's short-term public key, which is 32 bytes when using X25519.
	// This key is used to encrypt/decrypt DNS queries
	ResolverPk [keySize]byte

	// ResolverSk - the resolver's short-term private key, which is 32 bytes when using X25519.
	// Note that it's only used in the server implementation and never serialized/deserialized.
	// This key is used to encrypt/decrypt DNS queries
	ResolverSk [keySize]byte

	// ClientMagic - the first 8 bytes of a client query that is to be built
	// using the information from this certificate. It may be a truncated
	// public key. Two valid certificates cannot share the same <client-magic>.
	ClientMagic [clientMagicSize]byte

	// NotAfter - the date the certificate is valid from, as a big-endian
	// 4-byte unsigned Unix timestamp.
	NotBefore uint32

	// NotAfter - the date the certificate is valid until (inclusive), as a
	// big-endian 4-byte unsigned Unix timestamp.
	NotAfter uint32
}

Cert - DNSCrypt server certificate See ResolverConfig for more info on how to create one

func (*Cert) Deserialize

func (c *Cert) Deserialize(b []byte) error

Deserialize - deserializes certificate from a byte array <cert> ::= <cert-magic> <es-version> <protocol-minor-version> <signature>

<resolver-pk> <client-magic> <serial> <ts-start> <ts-end>
<extensions>

func (*Cert) Serialize

func (c *Cert) Serialize() ([]byte, error)

Serialize - serializes the cert to bytes <cert> ::= <cert-magic> <es-version> <protocol-minor-version> <signature>

<resolver-pk> <client-magic> <serial> <ts-start> <ts-end>
<extensions>

Certificates made of these information, without extensions, are 116 bytes long. With the addition of the cert-magic, es-version and protocol-minor-version, the record is 124 bytes long.

func (*Cert) Sign

func (c *Cert) Sign(privateKey ed25519.PrivateKey)

Sign - creates cert.Signature

func (*Cert) String

func (c *Cert) String() string

String - Cert's string representation

func (*Cert) VerifyDate

func (c *Cert) VerifyDate() bool

VerifyDate - checks that cert is valid at this moment

func (*Cert) VerifySignature

func (c *Cert) VerifySignature(publicKey ed25519.PublicKey) bool

VerifySignature - checks if the cert is properly signed with the specified signature

type Client

type Client struct {
	Net     string        // protocol (can be "udp" or "tcp", by default - "udp")
	Timeout time.Duration // read/write timeout
}

Client - DNSCrypt resolver client

func (*Client) Dial

func (c *Client) Dial(stampStr string) (*ResolverInfo, error)

Dial fetches and validates DNSCrypt certificate from the given server Data received during this call is then used for DNS requests encryption/decryption stampStr is an sdns:// address which is parsed using go-dnsstamps package

func (*Client) DialStamp

func (c *Client) DialStamp(stamp dnsstamps.ServerStamp) (*ResolverInfo, error)

DialStamp fetches and validates DNSCrypt certificate from the given server Data received during this call is then used for DNS requests encryption/decryption

func (*Client) Exchange

func (c *Client) Exchange(m *dns.Msg, resolverInfo *ResolverInfo) (*dns.Msg, error)

Exchange performs a synchronous DNS query to the specified DNSCrypt server and returns a DNS response. This method creates a new network connection for every call so avoid using it for TCP. DNSCrypt cert needs to be fetched and validated prior to this call using the c.DialStamp method.

func (*Client) ExchangeConn

func (c *Client) ExchangeConn(conn net.Conn, m *dns.Msg, resolverInfo *ResolverInfo) (*dns.Msg, error)

ExchangeConn performs a synchronous DNS query to the specified DNSCrypt server and returns a DNS response. DNSCrypt server information needs to be fetched and validated prior to this call using the c.DialStamp method

type CryptoConstruction

type CryptoConstruction uint16

CryptoConstruction represents the encryption algorithm (either XSalsa20Poly1305 or XChacha20Poly1305)

const (
	// UndefinedConstruction is the default value for empty CertInfo only
	UndefinedConstruction CryptoConstruction = iota
	// XSalsa20Poly1305 encryption
	XSalsa20Poly1305 CryptoConstruction = 0x0001
	// XChacha20Poly1305 encryption
	XChacha20Poly1305 CryptoConstruction = 0x0002
)

func (CryptoConstruction) String

func (c CryptoConstruction) String() string

type EncryptedQuery

type EncryptedQuery struct {
	// EsVersion - encryption to use
	EsVersion CryptoConstruction

	// ClientMagic - a 8 byte identifier for the resolver certificate
	// chosen by the client.
	ClientMagic [clientMagicSize]byte

	// ClientPk - the client's public key
	ClientPk [keySize]byte

	// With a 24 bytes nonce, a question sent by a DNSCrypt client must be
	// encrypted using the shared secret, and a nonce constructed as follows:
	// 12 bytes chosen by the client followed by 12 NUL (0) bytes.
	//
	// The client's half of the nonce can include a timestamp in addition to a
	// counter or to random bytes, so that when a response is received, the
	// client can use this timestamp to immediately discard responses to
	// queries that have been sent too long ago, or dated in the future.
	Nonce [nonceSize]byte
}

EncryptedQuery - a structure for encrypting and decrypting client queries

<dnscrypt-query> ::= <client-magic> <client-pk> <client-nonce> <encrypted-query> <encrypted-query> ::= AE(<shared-key> <client-nonce> <client-nonce-pad>, <client-query> <client-query-pad>)

func (*EncryptedQuery) Decrypt

func (q *EncryptedQuery) Decrypt(query []byte, serverSecretKey [keySize]byte) ([]byte, error)

Decrypt - decrypts the client query, returns decrypted DNS packet.

Please note, that before calling this method the following fields must be set: * ClientMagic -- to verify the query * EsVersion -- to decrypt

func (*EncryptedQuery) Encrypt

func (q *EncryptedQuery) Encrypt(packet []byte, sharedKey [sharedKeySize]byte) ([]byte, error)

Encrypt - encrypts the specified DNS query, returns encrypted data ready to be sent.

Note that this method will generate a random nonce automatically.

The following fields must be set before calling this method: * EsVersion -- to encrypt the query * ClientMagic -- to send it with the query * ClientPk -- to send it with the query

type EncryptedResponse

type EncryptedResponse struct {
	// EsVersion - encryption to use
	EsVersion CryptoConstruction

	// Nonce - <nonce> ::= <client-nonce> <resolver-nonce>
	// <client-nonce> ::= the nonce sent by the client in the related query.
	Nonce [nonceSize]byte
}

EncryptedResponse - structure for encrypting/decrypting server responses

<dnscrypt-response> ::= <resolver-magic> <nonce> <encrypted-response> <encrypted-response> ::= AE(<shared-key>, <nonce>, <resolver-response> <resolver-response-pad>)

func (*EncryptedResponse) Decrypt

func (r *EncryptedResponse) Decrypt(response []byte, sharedKey [sharedKeySize]byte) ([]byte, error)

Decrypt - decrypts the server response

EsVersion must be set.

func (*EncryptedResponse) Encrypt

func (r *EncryptedResponse) Encrypt(packet []byte, sharedKey [sharedKeySize]byte) ([]byte, error)

Encrypt - encrypts the server response

EsVersion must be set. Nonce needs to be set to "client-nonce". This method will generate "resolver-nonce" and set it automatically.

type Handler

type Handler interface {
	ServeDNS(rw ResponseWriter, r *dns.Msg) error
}

Handler is implemented by any value that implements ServeDNS.

var DefaultHandler Handler = &defaultHandler{
	udpClient: &dns.Client{
		Net:     "udp",
		Timeout: defaultTimeout,
	},
	tcpClient: &dns.Client{
		Net:     "tcp",
		Timeout: defaultTimeout,
	},
	addr: "94.140.14.140:53",
}

DefaultHandler - default Handler implementation that is used by Server if custom handler is not configured

type ResolverConfig

type ResolverConfig struct {
	// DNSCrypt provider name
	ProviderName string `yaml:"provider_name"`

	// PublicKey - DNSCrypt resolver public key
	PublicKey string `yaml:"public_key"`

	// PrivateKey - DNSCrypt resolver private key
	// The main and only purpose of this key is to sign the certificate
	PrivateKey string `yaml:"private_key"`

	// ResolverSk - hex-encoded short-term private key.
	// This key is used to encrypt/decrypt DNS queries.
	// If not set, we'll generate a new random ResolverSk and ResolverPk.
	ResolverSk string `yaml:"resolver_secret"`

	// ResolverSk - hex-encoded short-term public key corresponding to ResolverSk.
	// This key is used to encrypt/decrypt DNS queries.
	ResolverPk string `yaml:"resolver_public"`

	// EsVersion - crypto to use in this resolver
	EsVersion CryptoConstruction `yaml:"es_version"`

	// CertificateTTL - time-to-live for the certificate that is generated using this ResolverConfig.
	// If not set, we'll use 1 year by default.
	CertificateTTL time.Duration `yaml:"certificate_ttl"`
}

ResolverConfig - DNSCrypt resolver configuration

func GenerateResolverConfig

func GenerateResolverConfig(providerName string, privateKey ed25519.PrivateKey) (ResolverConfig, error)

GenerateResolverConfig - generates resolver configuration for a given provider name. providerName is mandatory. If needed, "2.dnscrypt-cert." prefix is added to it. privateKey is optional. If not set, it will be generated automatically.

func (*ResolverConfig) CreateCert

func (rc *ResolverConfig) CreateCert() (*Cert, error)

CreateCert - generates a signed Cert to be used by Server

func (*ResolverConfig) CreateStamp

func (rc *ResolverConfig) CreateStamp(addr string) (dnsstamps.ServerStamp, error)

CreateStamp - generates a DNS stamp for this resolver

type ResolverInfo

type ResolverInfo struct {
	SecretKey [keySize]byte // Client short-term secret key
	PublicKey [keySize]byte // Client short-term public key

	ServerPublicKey ed25519.PublicKey // Resolver public key (this key is used to validate cert signature)
	ServerAddress   string            // Server IP address
	ProviderName    string            // Provider name

	ResolverCert *Cert         // Certificate info (obtained with the first unencrypted DNS request)
	SharedKey    [keySize]byte // Shared key that is to be used to encrypt/decrypt messages
}

ResolverInfo contains DNSCrypt resolver information necessary for decryption/encryption

type ResponseWriter

type ResponseWriter interface {
	LocalAddr() net.Addr       // LocalAddr - local socket address
	RemoteAddr() net.Addr      // RemoteAddr - remote client socket address
	WriteMsg(m *dns.Msg) error // WriteMsg - writes response message to the client
}

ResponseWriter - interface that needs to be implemented for different protocols

type Server

type Server struct {
	// ProviderName - DNSCrypt provider name
	ProviderName string

	// ResolverCert - contains resolver certificate.
	ResolverCert *Cert

	// Handler to invoke. If nil, uses DefaultHandler.
	Handler Handler
}

Server - a simple DNSCrypt server implementation

func (*Server) ServeTCP

func (s *Server) ServeTCP(l net.Listener) error

ServeTCP - listens to TCP connections, queries are then processed by Server.Handler. It blocks the calling goroutine and to stop it you need to close the listener.

func (*Server) ServeUDP

func (s *Server) ServeUDP(l *net.UDPConn) error

ServeUDP - listens to UDP connections, queries are then processed by Server.Handler. It blocks the calling goroutine and to stop it you need to close the listener.

type TCPResponseWriter

type TCPResponseWriter struct {
	// contains filtered or unexported fields
}

TCPResponseWriter - ResponseWriter implementation for TCP

func (*TCPResponseWriter) LocalAddr

func (w *TCPResponseWriter) LocalAddr() net.Addr

LocalAddr - server socket local address

func (*TCPResponseWriter) RemoteAddr

func (w *TCPResponseWriter) RemoteAddr() net.Addr

RemoteAddr - client's address

func (*TCPResponseWriter) WriteMsg

func (w *TCPResponseWriter) WriteMsg(m *dns.Msg) error

WriteMsg - writes DNS message to the client

type UDPResponseWriter

type UDPResponseWriter struct {
	// contains filtered or unexported fields
}

UDPResponseWriter - ResponseWriter implementation for UDP

func (*UDPResponseWriter) LocalAddr

func (w *UDPResponseWriter) LocalAddr() net.Addr

LocalAddr - server socket local address

func (*UDPResponseWriter) RemoteAddr

func (w *UDPResponseWriter) RemoteAddr() net.Addr

RemoteAddr - client's address

func (*UDPResponseWriter) WriteMsg

func (w *UDPResponseWriter) WriteMsg(m *dns.Msg) error

WriteMsg - writes DNS message to the client

Directories

Path Synopsis
Package xsecretbox implements encryption/decryption of a message using specified keys
Package xsecretbox implements encryption/decryption of a message using specified keys

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL