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

107 lines
3 KiB
Go

package dnscrypt
import (
"bytes"
"encoding/binary"
"math/rand"
"time"
"dns.froth.zone/dnscrypt/xsecretbox"
"golang.org/x/crypto/nacl/secretbox"
)
// EncryptedResponse is a structure for encrypting/decrypting server responses
//
// <dnscrypt-response> ::= <resolver-magic> <nonce> <encrypted-response>
// <encrypted-response> ::= AE(<shared-key>, <nonce>, <resolver-response> <resolver-response-pad>)
type EncryptedResponse struct {
// EsVersion is the 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
}
// 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.
func (r *EncryptedResponse) Encrypt(packet []byte, sharedKey [sharedKeySize]byte) ([]byte, error) {
var response []byte
// Step 1: generate nonce
_, _ = rand.Read(r.Nonce[12:16])
binary.BigEndian.PutUint64(r.Nonce[16:nonceSize], uint64(time.Now().UnixNano()))
// Unencrypted part of the query:
response = append(response, resolverMagic[:]...)
response = append(response, r.Nonce[:]...)
// <resolver-response> <resolver-response-pad>
padded := pad(packet)
// <encrypted-response>
nonce := r.Nonce
if r.EsVersion == XChacha20Poly1305 {
response = xsecretbox.Seal(response, nonce[:], padded, sharedKey[:])
} else if r.EsVersion == XSalsa20Poly1305 {
var xsalsaNonce [nonceSize]byte
copy(xsalsaNonce[:], nonce[:])
response = secretbox.Seal(response, padded, &xsalsaNonce, &sharedKey)
} else {
return nil, ErrEsVersion
}
return response, nil
}
// Decrypt decrypts the server response
//
// EsVersion must be set.
func (r *EncryptedResponse) Decrypt(response []byte, sharedKey [sharedKeySize]byte) ([]byte, error) {
headerLength := len(resolverMagic) + nonceSize
if len(response) < headerLength+xsecretbox.TagSize+minDNSPacketSize {
return nil, ErrInvalidResponse
}
// read and verify <resolver-magic>
magic := [resolverMagicSize]byte{}
copy(magic[:], response[:resolverMagicSize])
if !bytes.Equal(magic[:], resolverMagic[:]) {
return nil, ErrInvalidResolverMagic
}
// read nonce
copy(r.Nonce[:], response[resolverMagicSize:nonceSize+resolverMagicSize])
// read and decrypt <encrypted-response>
encryptedResponse := response[nonceSize+resolverMagicSize:]
var packet []byte
var err error
if r.EsVersion == XChacha20Poly1305 {
packet, err = xsecretbox.Open(nil, r.Nonce[:], encryptedResponse, sharedKey[:])
if err != nil {
return nil, ErrInvalidResponse
}
} else if r.EsVersion == XSalsa20Poly1305 {
var xsalsaServerNonce [24]byte
copy(xsalsaServerNonce[:], r.Nonce[:])
var ok bool
packet, ok = secretbox.Open(nil, encryptedResponse, &xsalsaServerNonce, &sharedKey)
if !ok {
return nil, ErrInvalidResponse
}
} else {
return nil, ErrEsVersion
}
packet, err = unpad(packet)
if err != nil {
return nil, ErrInvalidPadding
}
return packet, nil
}