-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
simulator/encrypted-dns: DNSCrypt prototype
- Loading branch information
Showing
60 changed files
with
14,397 additions
and
5 deletions.
There are no files selected for viewing
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
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
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,91 @@ | ||
package dnscrypt | ||
|
||
import ( | ||
"bytes" | ||
"crypto/ed25519" | ||
"encoding/binary" | ||
"errors" | ||
) | ||
|
||
// Cert is a DNSCrypt server certificate. | ||
type Cert struct { | ||
// Serial is 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 is 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 is 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 is 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 is 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 is the date the certificate is valid from, as a big-endian | ||
// 4-byte unsigned Unix timestamp. | ||
NotBefore uint32 | ||
|
||
// NotAfter is the date the certificate is valid until (inclusive), as a | ||
// big-endian 4-byte unsigned Unix timestamp. | ||
NotAfter uint32 | ||
} | ||
|
||
// 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 (c *Cert) Deserialize(b []byte) error { | ||
if len(b) < 124 { | ||
return errors.New(ErrCertTooShort) | ||
} | ||
|
||
// <cert-magic> | ||
if !bytes.Equal(b[:4], certMagic[:4]) { | ||
return errors.New(ErrCertMagic) | ||
} | ||
|
||
// <es-version> | ||
switch esVersion := binary.BigEndian.Uint16(b[4:6]); esVersion { | ||
case uint16(XSalsa20Poly1305): | ||
c.EsVersion = XSalsa20Poly1305 | ||
case uint16(XChacha20Poly1305): | ||
c.EsVersion = XChacha20Poly1305 | ||
default: | ||
return errors.New(ErrEsVersion) | ||
} | ||
|
||
// Ignore 6:8, <protocol-minor-version> | ||
// <signature> | ||
copy(c.Signature[:], b[8:72]) | ||
// <resolver-pk> | ||
copy(c.ResolverPk[:], b[72:104]) | ||
// <client-magic> | ||
copy(c.ClientMagic[:], b[104:112]) | ||
// <serial> | ||
c.Serial = binary.BigEndian.Uint32(b[112:116]) | ||
// <ts-start> <ts-end> | ||
c.NotBefore = binary.BigEndian.Uint32(b[116:120]) | ||
c.NotAfter = binary.BigEndian.Uint32(b[120:124]) | ||
|
||
// Deserialized with no issues | ||
return nil | ||
} |
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,149 @@ | ||
package dnscrypt | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"crypto/ed25519" | ||
"errors" | ||
"net" | ||
"strings" | ||
"time" | ||
|
||
"github.com/alphasoc/flightsim/simulator/encdns" | ||
dnsstamps "github.com/jedisct1/go-dnsstamps" | ||
"golang.org/x/net/dns/dnsmessage" | ||
) | ||
|
||
// Basic Client struct. Wrapped by providers. | ||
type Client struct { | ||
Net string | ||
} | ||
|
||
// ResolverInfo contains DNSCrypt resolver information necessary for decryption/encryption. | ||
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 | ||
} | ||
|
||
// findCertMagic is a bit of a hack to find the beginning of the certificate. Returns | ||
// the start of the certificate magic, or -1 if not found. | ||
func findCertMagic(b []byte) int { | ||
return bytes.Index(b, certMagic[0:]) | ||
} | ||
|
||
// fetchCert loads DNSCrypt cert from the specified server. | ||
func (c *Client) fetchCert(ctx context.Context, stamp dnsstamps.ServerStamp) (*Cert, error) { | ||
providerName := stamp.ProviderName | ||
if !strings.HasSuffix(providerName, ".") { | ||
providerName = providerName + "." | ||
} | ||
|
||
dnsReq, err := encdns.NewUDPRequest(providerName, dnsmessage.TypeTXT) | ||
if err != nil { | ||
return nil, err | ||
} | ||
d := net.Dialer{} | ||
// ctx, cancelFn := context.WithTimeout(ctx, 500*time.Millisecond) | ||
// defer cancelFn() | ||
conn, err := d.DialContext(ctx, c.Net, stamp.ServerAddrStr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer conn.Close() | ||
_, err = conn.Write(dnsReq) | ||
if err != nil { | ||
return nil, err | ||
} | ||
b := make([]byte, 2048) | ||
conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) | ||
n, err := conn.Read(b) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// Check certificate response rcode==0. | ||
certMsg := dnsmessage.Message{} | ||
if err := certMsg.Unpack(b[0:n]); err != nil || certMsg.RCode != dnsmessage.RCodeSuccess { | ||
return nil, errors.New(ErrInvalidDNSResponse) | ||
} | ||
certIdx := findCertMagic(b) | ||
if certIdx == -1 { | ||
return nil, errors.New(ErrCertMagic) | ||
} | ||
certStr := b[certIdx:] | ||
cert := &Cert{} | ||
err = cert.Deserialize(certStr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return cert, nil | ||
} | ||
|
||
// Dial dials the server specified by stampStr, returning a *ResolverInfo and an error. | ||
func (c *Client) Dial(ctx context.Context, stampStr string) (*ResolverInfo, error) { | ||
stamp, err := dnsstamps.NewServerStampFromString(stampStr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if stamp.Proto != dnsstamps.StampProtoTypeDNSCrypt { | ||
return nil, errors.New(ErrInvalidDNSStamp) | ||
} | ||
resolverInfo := &ResolverInfo{} | ||
// Generate the secret/public pair. | ||
resolverInfo.SecretKey, resolverInfo.PublicKey = generateRandomKeyPair() | ||
// Set the provider properties. | ||
resolverInfo.ServerPublicKey = stamp.ServerPk | ||
resolverInfo.ServerAddress = stamp.ServerAddrStr | ||
resolverInfo.ProviderName = stamp.ProviderName | ||
cert, err := c.fetchCert(ctx, stamp) | ||
if err != nil { | ||
return nil, err | ||
} | ||
resolverInfo.ResolverCert = cert | ||
// Compute shared key that we'll use to encrypt/decrypt messages. | ||
sharedKey, err := computeSharedKey(cert.EsVersion, &resolverInfo.SecretKey, &cert.ResolverPk) | ||
if err != nil { | ||
return nil, err | ||
} | ||
resolverInfo.SharedKey = sharedKey | ||
return resolverInfo, nil | ||
} | ||
|
||
// Encrypt encrypts a DNS message using shared key from the resolver info. It returns a | ||
// []byte and an error. | ||
func (c *Client) Encrypt(m []byte, resolverInfo *ResolverInfo) ([]byte, error) { | ||
q := EncryptedQuery{ | ||
EsVersion: resolverInfo.ResolverCert.EsVersion, | ||
ClientMagic: resolverInfo.ResolverCert.ClientMagic, | ||
ClientPk: resolverInfo.PublicKey, | ||
} | ||
// query, err := m.Pack() | ||
// if err != nil { | ||
// return nil, err | ||
// } | ||
b, err := q.Encrypt(m, resolverInfo.SharedKey) | ||
if len(b) > MinMsgSize { | ||
return nil, errors.New(ErrQueryTooLarge) | ||
} | ||
|
||
return b, err | ||
} | ||
|
||
// decrypts decrypts a DNS message using a shared key from the resolver info. It returns | ||
// a []byte and an error. | ||
func (c *Client) Decrypt(b []byte, resolverInfo *ResolverInfo) ([]byte, error) { | ||
dr := EncryptedResponse{ | ||
EsVersion: resolverInfo.ResolverCert.EsVersion, | ||
} | ||
msg, err := dr.Decrypt(b, resolverInfo.SharedKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return msg, nil | ||
} |
Oops, something went wrong.