1
0
Fork 0
mirror of https://github.com/SamTherapy/dnscrypt.git synced 2024-07-05 06:46:07 +00:00
dnscrypt/encrypted_response.go
Andrey Meshkov 5ddb58f703
Graceful shutdown of the DNSCrypt server (#6)
Graceful shutdown of the DNSCrypt server

This PR implements Server.Shutdown(ctx context.Context) method that allows
to shut down the DNSCrypt server gracefully.

Some additional changes that were inadvertently made while doing that:
1. Added benchmark tests
2. Started using dns.ReadFromSessionUDP / dns.WriteToSessionUDP instead of implementing it by ourselves
3. Generally improved tests
4. Added depguard 
5. Improved comments overall in the code
2021-03-19 15:42:48 +03:00

107 lines
3 KiB
Go

package dnscrypt
import (
"bytes"
"encoding/binary"
"math/rand"
"time"
"github.com/ameshkov/dnscrypt/v2/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
}