Skip to content

Commit 7b29055

Browse files
committed
simulator/encrypted-dns: DNSCrypt prototype
1 parent 62609ec commit 7b29055

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+14397
-5
lines changed

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ module github.com/alphasoc/flightsim
33
go 1.13
44

55
require (
6+
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
7+
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 // indirect
68
github.com/cretz/bine v0.1.1-0.20191105223159-1c71414a61dc
9+
github.com/jedisct1/go-dnsstamps v0.0.0-20210810213811-61cc83d2a354
710
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7
811
github.com/stretchr/testify v1.4.0 // indirect
9-
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c // indirect
12+
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c
1013
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933
1114
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
1215
)

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
2+
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
3+
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
4+
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
15
github.com/cretz/bine v0.1.1-0.20191105223159-1c71414a61dc h1:Y2CvXooSQ+ey3x6Yi4B4pxK+QCLa4dOpxIhjeMTt3dA=
26
github.com/cretz/bine v0.1.1-0.20191105223159-1c71414a61dc/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
37
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
48
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9+
github.com/jedisct1/go-dnsstamps v0.0.0-20210810213811-61cc83d2a354 h1:sIB9mDh2spQdh95jeXF2h9uSNtObbehD0YbDCzmqbM8=
10+
github.com/jedisct1/go-dnsstamps v0.0.0-20210810213811-61cc83d2a354/go.mod h1:t35n6rsPE3nD3RXbc5hI5Ax1ci/SSYTpx0BdMXh/1aE=
511
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7 h1:RcqIXZDN7Vz5lgK7+0h3MFF2JNgEu4h91palXJLJ354=
612
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
713
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

simulator/encdns/dnscrypt/cert.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package dnscrypt
2+
3+
import (
4+
"bytes"
5+
"crypto/ed25519"
6+
"encoding/binary"
7+
"errors"
8+
)
9+
10+
// Cert is a DNSCrypt server certificate.
11+
type Cert struct {
12+
// Serial is a 4 byte serial number in big-endian format. If more than
13+
// one certificates are valid, the client must prefer the certificate
14+
// with a higher serial number.
15+
Serial uint32
16+
17+
// <es-version> ::= the cryptographic construction to use with this
18+
// certificate.
19+
// For X25519-XSalsa20Poly1305, <es-version> must be 0x00 0x01.
20+
// For X25519-XChacha20Poly1305, <es-version> must be 0x00 0x02.
21+
EsVersion CryptoConstruction
22+
23+
// Signature is a 64-byte signature of (<resolver-pk> <client-magic>
24+
// <serial> <ts-start> <ts-end> <extensions>) using the Ed25519 algorithm and the
25+
// provider secret key. Ed25519 must be used in this version of the
26+
// protocol.
27+
Signature [ed25519.SignatureSize]byte
28+
29+
// ResolverPk is the resolver's short-term public key, which is 32 bytes when using X25519.
30+
// This key is used to encrypt/decrypt DNS queries
31+
ResolverPk [keySize]byte
32+
33+
// ResolverSk is the resolver's short-term private key, which is 32 bytes when using X25519.
34+
// Note that it's only used in the server implementation and never serialized/deserialized.
35+
// This key is used to encrypt/decrypt DNS queries
36+
ResolverSk [keySize]byte
37+
38+
// ClientMagic is the first 8 bytes of a client query that is to be built
39+
// using the information from this certificate. It may be a truncated
40+
// public key. Two valid certificates cannot share the same <client-magic>.
41+
ClientMagic [clientMagicSize]byte
42+
43+
// NotAfter is the date the certificate is valid from, as a big-endian
44+
// 4-byte unsigned Unix timestamp.
45+
NotBefore uint32
46+
47+
// NotAfter is the date the certificate is valid until (inclusive), as a
48+
// big-endian 4-byte unsigned Unix timestamp.
49+
NotAfter uint32
50+
}
51+
52+
// Deserialize deserializes certificate from a byte array
53+
// <cert> ::= <cert-magic> <es-version> <protocol-minor-version> <signature>
54+
// <resolver-pk> <client-magic> <serial> <ts-start> <ts-end>
55+
// <extensions>
56+
func (c *Cert) Deserialize(b []byte) error {
57+
if len(b) < 124 {
58+
return errors.New(ErrCertTooShort)
59+
}
60+
61+
// <cert-magic>
62+
if !bytes.Equal(b[:4], certMagic[:4]) {
63+
return errors.New(ErrCertMagic)
64+
}
65+
66+
// <es-version>
67+
switch esVersion := binary.BigEndian.Uint16(b[4:6]); esVersion {
68+
case uint16(XSalsa20Poly1305):
69+
c.EsVersion = XSalsa20Poly1305
70+
case uint16(XChacha20Poly1305):
71+
c.EsVersion = XChacha20Poly1305
72+
default:
73+
return errors.New(ErrEsVersion)
74+
}
75+
76+
// Ignore 6:8, <protocol-minor-version>
77+
// <signature>
78+
copy(c.Signature[:], b[8:72])
79+
// <resolver-pk>
80+
copy(c.ResolverPk[:], b[72:104])
81+
// <client-magic>
82+
copy(c.ClientMagic[:], b[104:112])
83+
// <serial>
84+
c.Serial = binary.BigEndian.Uint32(b[112:116])
85+
// <ts-start> <ts-end>
86+
c.NotBefore = binary.BigEndian.Uint32(b[116:120])
87+
c.NotAfter = binary.BigEndian.Uint32(b[120:124])
88+
89+
// Deserialized with no issues
90+
return nil
91+
}

simulator/encdns/dnscrypt/client.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package dnscrypt
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"crypto/ed25519"
7+
"errors"
8+
"net"
9+
"strings"
10+
"time"
11+
12+
"github.com/alphasoc/flightsim/simulator/encdns"
13+
dnsstamps "github.com/jedisct1/go-dnsstamps"
14+
"golang.org/x/net/dns/dnsmessage"
15+
)
16+
17+
// Basic Client struct. Wrapped by providers.
18+
type Client struct {
19+
Net string
20+
}
21+
22+
// ResolverInfo contains DNSCrypt resolver information necessary for decryption/encryption.
23+
type ResolverInfo struct {
24+
SecretKey [keySize]byte // Client short-term secret key
25+
PublicKey [keySize]byte // Client short-term public key
26+
27+
ServerPublicKey ed25519.PublicKey // Resolver public key (this key is used to validate cert signature)
28+
ServerAddress string // Server IP address
29+
ProviderName string // Provider name
30+
31+
ResolverCert *Cert // Certificate info (obtained with the first unencrypted DNS request)
32+
SharedKey [keySize]byte // Shared key that is to be used to encrypt/decrypt messages
33+
}
34+
35+
// findCertMagic is a bit of a hack to find the beginning of the certificate. Returns
36+
// the start of the certificate magic, or -1 if not found.
37+
func findCertMagic(b []byte) int {
38+
return bytes.Index(b, certMagic[0:])
39+
}
40+
41+
// fetchCert loads DNSCrypt cert from the specified server.
42+
func (c *Client) fetchCert(ctx context.Context, stamp dnsstamps.ServerStamp) (*Cert, error) {
43+
providerName := stamp.ProviderName
44+
if !strings.HasSuffix(providerName, ".") {
45+
providerName = providerName + "."
46+
}
47+
48+
dnsReq, err := encdns.NewUDPRequest(providerName, dnsmessage.TypeTXT)
49+
if err != nil {
50+
return nil, err
51+
}
52+
d := net.Dialer{}
53+
// ctx, cancelFn := context.WithTimeout(ctx, 500*time.Millisecond)
54+
// defer cancelFn()
55+
conn, err := d.DialContext(ctx, c.Net, stamp.ServerAddrStr)
56+
if err != nil {
57+
return nil, err
58+
}
59+
defer conn.Close()
60+
_, err = conn.Write(dnsReq)
61+
if err != nil {
62+
return nil, err
63+
}
64+
b := make([]byte, 2048)
65+
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
66+
n, err := conn.Read(b)
67+
if err != nil {
68+
return nil, err
69+
}
70+
// Check certificate response rcode==0.
71+
certMsg := dnsmessage.Message{}
72+
if err := certMsg.Unpack(b[0:n]); err != nil || certMsg.RCode != dnsmessage.RCodeSuccess {
73+
return nil, errors.New(ErrInvalidDNSResponse)
74+
}
75+
certIdx := findCertMagic(b)
76+
if certIdx == -1 {
77+
return nil, errors.New(ErrCertMagic)
78+
}
79+
certStr := b[certIdx:]
80+
cert := &Cert{}
81+
err = cert.Deserialize(certStr)
82+
if err != nil {
83+
return nil, err
84+
}
85+
return cert, nil
86+
}
87+
88+
// Dial dials the server specified by stampStr, returning a *ResolverInfo and an error.
89+
func (c *Client) Dial(ctx context.Context, stampStr string) (*ResolverInfo, error) {
90+
stamp, err := dnsstamps.NewServerStampFromString(stampStr)
91+
if err != nil {
92+
return nil, err
93+
}
94+
if stamp.Proto != dnsstamps.StampProtoTypeDNSCrypt {
95+
return nil, errors.New(ErrInvalidDNSStamp)
96+
}
97+
resolverInfo := &ResolverInfo{}
98+
// Generate the secret/public pair.
99+
resolverInfo.SecretKey, resolverInfo.PublicKey = generateRandomKeyPair()
100+
// Set the provider properties.
101+
resolverInfo.ServerPublicKey = stamp.ServerPk
102+
resolverInfo.ServerAddress = stamp.ServerAddrStr
103+
resolverInfo.ProviderName = stamp.ProviderName
104+
cert, err := c.fetchCert(ctx, stamp)
105+
if err != nil {
106+
return nil, err
107+
}
108+
resolverInfo.ResolverCert = cert
109+
// Compute shared key that we'll use to encrypt/decrypt messages.
110+
sharedKey, err := computeSharedKey(cert.EsVersion, &resolverInfo.SecretKey, &cert.ResolverPk)
111+
if err != nil {
112+
return nil, err
113+
}
114+
resolverInfo.SharedKey = sharedKey
115+
return resolverInfo, nil
116+
}
117+
118+
// Encrypt encrypts a DNS message using shared key from the resolver info. It returns a
119+
// []byte and an error.
120+
func (c *Client) Encrypt(m []byte, resolverInfo *ResolverInfo) ([]byte, error) {
121+
q := EncryptedQuery{
122+
EsVersion: resolverInfo.ResolverCert.EsVersion,
123+
ClientMagic: resolverInfo.ResolverCert.ClientMagic,
124+
ClientPk: resolverInfo.PublicKey,
125+
}
126+
// query, err := m.Pack()
127+
// if err != nil {
128+
// return nil, err
129+
// }
130+
b, err := q.Encrypt(m, resolverInfo.SharedKey)
131+
if len(b) > MinMsgSize {
132+
return nil, errors.New(ErrQueryTooLarge)
133+
}
134+
135+
return b, err
136+
}
137+
138+
// decrypts decrypts a DNS message using a shared key from the resolver info. It returns
139+
// a []byte and an error.
140+
func (c *Client) Decrypt(b []byte, resolverInfo *ResolverInfo) ([]byte, error) {
141+
dr := EncryptedResponse{
142+
EsVersion: resolverInfo.ResolverCert.EsVersion,
143+
}
144+
msg, err := dr.Decrypt(b, resolverInfo.SharedKey)
145+
if err != nil {
146+
return nil, err
147+
}
148+
return msg, nil
149+
}

0 commit comments

Comments
 (0)