1
0
Fork 0
mirror of https://github.com/SamTherapy/dnscrypt.git synced 2024-07-02 21:56:06 +00:00
dnscrypt/encrypted_query.go

138 lines
4.2 KiB
Go

package dnscrypt
import (
"bytes"
"encoding/binary"
"math/rand"
"time"
"dns.froth.zone/dnscrypt/xsecretbox"
"golang.org/x/crypto/nacl/secretbox"
)
// EncryptedQuery is 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>)
type EncryptedQuery struct {
// EsVersion is the encryption to use
EsVersion CryptoConstruction
// ClientMagic is a 8 byte identifier for the resolver certificate
// chosen by the client.
ClientMagic [clientMagicSize]byte
// ClientPk is 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
}
// 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
func (q *EncryptedQuery) Encrypt(packet []byte, sharedKey [sharedKeySize]byte) ([]byte, error) {
var query []byte
// Step 1: generate nonce
binary.BigEndian.PutUint64(q.Nonce[:8], uint64(time.Now().UnixNano()))
_, _ = rand.Read(q.Nonce[8:12])
// Unencrypted part of the query:
// <client-magic> <client-pk> <client-nonce>
query = append(query, q.ClientMagic[:]...)
query = append(query, q.ClientPk[:]...)
query = append(query, q.Nonce[:nonceSize/2]...)
// <client-query> <client-query-pad>
padded := pad(packet)
// <encrypted-query>
nonce := q.Nonce
if q.EsVersion == XChacha20Poly1305 {
query = xsecretbox.Seal(query, nonce[:], padded, sharedKey[:])
} else if q.EsVersion == XSalsa20Poly1305 {
var xsalsaNonce [nonceSize]byte
copy(xsalsaNonce[:], nonce[:])
query = secretbox.Seal(query, padded, &xsalsaNonce, &sharedKey)
} else {
return nil, ErrEsVersion
}
return query, nil
}
// 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 (q *EncryptedQuery) Decrypt(query []byte, serverSecretKey [keySize]byte) ([]byte, error) {
headerLength := clientMagicSize + keySize + nonceSize/2
if len(query) < headerLength+xsecretbox.TagSize+minDNSPacketSize {
return nil, ErrInvalidQuery
}
// read and verify <client-magic>
clientMagic := [clientMagicSize]byte{}
copy(clientMagic[:], query[:clientMagicSize])
if !bytes.Equal(clientMagic[:], q.ClientMagic[:]) {
return nil, ErrInvalidClientMagic
}
// read <client-pk>
idx := clientMagicSize
copy(q.ClientPk[:keySize], query[idx:idx+keySize])
// generate server shared key
sharedKey, err := computeSharedKey(q.EsVersion, &serverSecretKey, &q.ClientPk)
if err != nil {
return nil, err
}
// read <client-nonce>
idx = idx + keySize
copy(q.Nonce[:nonceSize/2], query[idx:idx+nonceSize/2])
// read and decrypt <encrypted-query>
idx = idx + nonceSize/2
encryptedQuery := query[idx:]
var packet []byte
if q.EsVersion == XChacha20Poly1305 {
packet, err = xsecretbox.Open(nil, q.Nonce[:], encryptedQuery, sharedKey[:])
if err != nil {
return nil, ErrInvalidQuery
}
} else if q.EsVersion == XSalsa20Poly1305 {
var xsalsaServerNonce [24]byte
copy(xsalsaServerNonce[:], q.Nonce[:])
var ok bool
packet, ok = secretbox.Open(nil, encryptedQuery, &xsalsaServerNonce, &sharedKey)
if !ok {
return nil, ErrInvalidQuery
}
} else {
return nil, ErrEsVersion
}
packet, err = unpad(packet)
if err != nil {
return nil, ErrInvalidPadding
}
return packet, nil
}