summaryrefslogtreecommitdiffstats
path: root/dnscrypt.hh
blob: ff4d94c4663965861c140ac4218e7253dbd191b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/*
 * This file is part of PowerDNS or dnsdist.
 * Copyright -- PowerDNS.COM B.V. and its contributors
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * In addition, for the avoidance of any doubt, permission is granted to
 * link this program with OpenSSL and to (re)distribute the binaries
 * produced as the result of such linking.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
#pragma once
#include "config.h"

#ifndef HAVE_DNSCRYPT

/* let's just define a few types and values so that the rest of
   the code can ignore whether DNSCrypt support is available */
#define DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE (0)

class DNSCryptContext
{
};

class DNSCryptQuery
{
  DNSCryptQuery(const std::shared_ptr<DNSCryptContext>& ctx): d_ctx(ctx)
  {
  }
private:
  std::shared_ptr<DNSCryptContext> d_ctx{nullptr};
};

#else /* HAVE_DNSCRYPT */

#include <memory>
#include <string>
#include <vector>
#include <arpa/inet.h>

#include <sodium.h>

#include "dnsname.hh"
#include "lock.hh"
#include "noinitvector.hh"

#define DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE (crypto_sign_ed25519_PUBLICKEYBYTES)
#define DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE (crypto_sign_ed25519_SECRETKEYBYTES)
#define DNSCRYPT_SIGNATURE_SIZE (crypto_sign_ed25519_BYTES)

#define DNSCRYPT_PUBLIC_KEY_SIZE (crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES)
#define DNSCRYPT_PRIVATE_KEY_SIZE (crypto_box_curve25519xsalsa20poly1305_SECRETKEYBYTES)
#define DNSCRYPT_NONCE_SIZE (crypto_box_curve25519xsalsa20poly1305_NONCEBYTES)
#define DNSCRYPT_BEFORENM_SIZE (crypto_box_curve25519xsalsa20poly1305_BEFORENMBYTES)
#define DNSCRYPT_MAC_SIZE (crypto_box_curve25519xsalsa20poly1305_MACBYTES)

#ifdef HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY
static_assert(crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES == crypto_box_curve25519xchacha20poly1305_PUBLICKEYBYTES, "DNSCrypt public key size should be the same for all exchange versions");
static_assert(crypto_box_curve25519xsalsa20poly1305_SECRETKEYBYTES == crypto_box_curve25519xchacha20poly1305_SECRETKEYBYTES, "DNSCrypt private key size should be the same for all exchange versions");
static_assert(crypto_box_curve25519xchacha20poly1305_NONCEBYTES == crypto_box_curve25519xsalsa20poly1305_NONCEBYTES, "DNSCrypt nonce size should be the same for all exchange versions");
static_assert(crypto_box_curve25519xsalsa20poly1305_MACBYTES == crypto_box_curve25519xchacha20poly1305_MACBYTES, "DNSCrypt MAC size should be the same for all exchange versions");
static_assert(crypto_box_curve25519xchacha20poly1305_BEFORENMBYTES == crypto_box_curve25519xsalsa20poly1305_BEFORENMBYTES, "DNSCrypt BEFORENM size should be the same for all exchange versions");
#endif /* HAVE_CRYPTO_BOX_CURVE25519XCHACHA20POLY1305_EASY */

#define DNSCRYPT_CERT_MAGIC_SIZE (4)
#define DNSCRYPT_CERT_MAGIC_VALUE { 0x44, 0x4e, 0x53, 0x43 }
#define DNSCRYPT_CERT_PROTOCOL_MINOR_VERSION_VALUE { 0x00, 0x00 }
#define DNSCRYPT_CLIENT_MAGIC_SIZE (8)
#define DNSCRYPT_RESOLVER_MAGIC { 0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38 }
#define DNSCRYPT_RESOLVER_MAGIC_SIZE (8)
#define DNSCRYPT_PADDED_BLOCK_SIZE (64)
#define DNSCRYPT_MAX_TCP_PADDING_SIZE (256)
#define DNSCRYPT_MAX_RESPONSE_PADDING_SIZE (256)
#define DNSCRYPT_MAX_RESPONSE_PADDING_AND_MAC_SIZE (DNSCRYPT_MAX_RESPONSE_PADDING_SIZE + DNSCRYPT_MAC_SIZE)

/* "The client must check for new certificates every hour", so let's use one hour TTL */
#define DNSCRYPT_CERTIFICATE_RESPONSE_TTL (3600)

static_assert(DNSCRYPT_CLIENT_MAGIC_SIZE <= DNSCRYPT_PUBLIC_KEY_SIZE, "DNSCrypt Client Nonce size should be smaller or equal to public key size.");

#define DNSCRYPT_CERT_ES_VERSION1_VALUE { 0x00, 0x01 }
#define DNSCRYPT_CERT_ES_VERSION2_VALUE { 0x00, 0x02 }

class DNSCryptContext;

struct DNSCryptCertSignedData
{
  unsigned char resolverPK[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
  unsigned char clientMagic[DNSCRYPT_CLIENT_MAGIC_SIZE];
  uint32_t serial;
  uint32_t tsStart;
  uint32_t tsEnd;
};

class DNSCryptCert
{
public:
  uint32_t getSerial() const
  {
    return ntohl(signedData.serial);
  }
  uint32_t getTSStart() const
  {
    return signedData.tsStart;
  }
  uint32_t getTSEnd() const
  {
    return signedData.tsEnd;
  }
  bool isValid(time_t now) const
  {
    return ntohl(getTSStart()) <= static_cast<uint32_t>(now) && static_cast<uint32_t>(now) <= ntohl(getTSEnd());
  }
  unsigned char magic[DNSCRYPT_CERT_MAGIC_SIZE];
  unsigned char esVersion[2];
  unsigned char protocolMinorVersion[2];
  unsigned char signature[DNSCRYPT_SIGNATURE_SIZE];
  struct DNSCryptCertSignedData signedData;
};

static_assert((sizeof(DNSCryptCertSignedData) + DNSCRYPT_SIGNATURE_SIZE) == 116, "Dnscrypt cert signed data size + signature size should be 116!");
static_assert(sizeof(DNSCryptCert) == 124, "Dnscrypt cert size should be 124!");

struct DNSCryptQueryHeader
{
  unsigned char clientMagic[DNSCRYPT_CLIENT_MAGIC_SIZE];
  unsigned char clientPK[DNSCRYPT_PUBLIC_KEY_SIZE];
  unsigned char clientNonce[DNSCRYPT_NONCE_SIZE / 2];
};

static_assert(sizeof(DNSCryptQueryHeader) == 52, "Dnscrypt query header size should be 52!");

struct DNSCryptResponseHeader
{
  const unsigned char resolverMagic[DNSCRYPT_RESOLVER_MAGIC_SIZE] = DNSCRYPT_RESOLVER_MAGIC;
  unsigned char nonce[DNSCRYPT_NONCE_SIZE];
};

typedef enum {
  VERSION1,
  VERSION2
} DNSCryptExchangeVersion;

class DNSCryptPrivateKey
{
public:
  DNSCryptPrivateKey();
  ~DNSCryptPrivateKey();
  void loadFromFile(const std::string& keyFile);
  void saveToFile(const std::string& keyFile) const;

  unsigned char key[DNSCRYPT_PRIVATE_KEY_SIZE];
};

struct DNSCryptCertificatePair
{
  unsigned char publicKey[DNSCRYPT_PUBLIC_KEY_SIZE];
  DNSCryptCert cert;
  DNSCryptPrivateKey privateKey;
  bool active;
};

class DNSCryptQuery
{
public:
  DNSCryptQuery(const std::shared_ptr<DNSCryptContext>& ctx): d_ctx(ctx)
  {
    memset(&d_header, 0, sizeof(d_header));
#ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM
    memset(&d_sharedKey, 0, sizeof(d_sharedKey));
#endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
  }

  ~DNSCryptQuery();

  bool isValid() const
  {
    return d_valid;
  }

  const DNSName& getQName() const
  {
    return d_qname;
  }

  uint16_t getID() const
  {
    return d_id;
  }

  const unsigned char* getClientMagic() const
  {
    return d_header.clientMagic;
  }

  bool isEncrypted() const
  {
    return d_encrypted;
  }

  void setCertificatePair(const std::shared_ptr<DNSCryptCertificatePair>& pair)
  {
    d_pair = pair;
  }

  void parsePacket(PacketBuffer& packet, bool tcp, time_t now);
  void getDecrypted(bool tcp, PacketBuffer& packet);
  void getCertificateResponse(time_t now, PacketBuffer& response) const;
  int encryptResponse(PacketBuffer& response, size_t maxResponseSize, bool tcp);

  static const size_t s_minUDPLength = 256;

private:
  DNSCryptExchangeVersion getVersion() const;
#ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM
  int computeSharedKey();
#endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
  void fillServerNonce(unsigned char* dest) const;
  uint16_t computePaddingSize(uint16_t unpaddedLen, size_t maxLen) const;
  bool parsePlaintextQuery(const PacketBuffer& packet);
  bool isEncryptedQuery(const PacketBuffer& packet, bool tcp, time_t now);

  DNSCryptQueryHeader d_header;
#ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM
  unsigned char d_sharedKey[crypto_box_BEFORENMBYTES];
#endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
  DNSName d_qname;
  std::shared_ptr<DNSCryptContext> d_ctx{nullptr};
  std::shared_ptr<DNSCryptCertificatePair> d_pair{nullptr};
  uint16_t d_id{0};
  uint16_t d_len{0};
  uint16_t d_paddedLen{0};
  bool d_encrypted{false};
  bool d_valid{false};

#ifdef HAVE_CRYPTO_BOX_EASY_AFTERNM
  bool d_sharedKeyComputed{false};
#endif /* HAVE_CRYPTO_BOX_EASY_AFTERNM */
};

class DNSCryptContext
{
public:
  static void generateProviderKeys(unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE], unsigned char privateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE]);
  static std::string getProviderFingerprint(unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE]);
  static void generateCertificate(uint32_t serial, time_t begin, time_t end, const DNSCryptExchangeVersion& version, const unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE], DNSCryptPrivateKey& privateKey, DNSCryptCert& cert);
  static void saveCertFromFile(const DNSCryptCert& cert, const std::string&filename);
  static std::string certificateDateToStr(uint32_t date);
  static void generateResolverKeyPair(DNSCryptPrivateKey& privK, unsigned char pubK[DNSCRYPT_PUBLIC_KEY_SIZE]);
  static void setExchangeVersion(const DNSCryptExchangeVersion& version,  unsigned char esVersion[sizeof(DNSCryptCert::esVersion)]);
  static DNSCryptExchangeVersion getExchangeVersion(const unsigned char esVersion[sizeof(DNSCryptCert::esVersion)]);
  static DNSCryptExchangeVersion getExchangeVersion(const DNSCryptCert& cert);

  struct CertKeyPaths
  {
    std::string cert;
    std::string key;
  };

  DNSCryptContext(const std::string& pName, const std::vector<CertKeyPaths>& certKeys);
  DNSCryptContext(const std::string& pName, const DNSCryptCert& certificate, const DNSCryptPrivateKey& pKey);
  ~DNSCryptContext();
  
  void reloadCertificates();
  void loadNewCertificate(const std::string& certFile, const std::string& keyFile, bool active=true, bool reload=false);
  void addNewCertificate(const DNSCryptCert& newCert, const DNSCryptPrivateKey& newKey, bool active=true, bool reload=false);

  void markActive(uint32_t serial);
  void markInactive(uint32_t serial);
  void removeInactiveCertificate(uint32_t serial);
  std::vector<std::shared_ptr<DNSCryptCertificatePair>> getCertificates();
  const DNSName& getProviderName() const { return providerName; }

  int encryptQuery(PacketBuffer& query, size_t maximumSize, const unsigned char clientPublicKey[DNSCRYPT_PUBLIC_KEY_SIZE], const DNSCryptPrivateKey& clientPrivateKey, const unsigned char clientNonce[DNSCRYPT_NONCE_SIZE / 2], bool tcp, const std::shared_ptr<DNSCryptCert>& cert) const;
  bool magicMatchesAPublicKey(DNSCryptQuery& query, time_t now);
  void getCertificateResponse(time_t now, const DNSName& qname, uint16_t qid, PacketBuffer& response);

private:
  static void computePublicKeyFromPrivate(const DNSCryptPrivateKey& privK, unsigned char pubK[DNSCRYPT_PUBLIC_KEY_SIZE]);
  static void loadCertFromFile(const std::string&filename, DNSCryptCert& dest);
  static std::shared_ptr<DNSCryptCertificatePair> loadCertificatePair(const std::string& certFile, const std::string& keyFile);

  void addNewCertificate(std::shared_ptr<DNSCryptCertificatePair>& newCert, bool reload=false);

  SharedLockGuarded<std::vector<std::shared_ptr<DNSCryptCertificatePair>>> d_certs;
  SharedLockGuarded<std::vector<CertKeyPaths>> d_certKeyPaths;
  DNSName providerName;
};

bool generateDNSCryptCertificate(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, DNSCryptExchangeVersion version, DNSCryptCert& certOut, DNSCryptPrivateKey& keyOut);

#endif