]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ednscookies.cc
Merge pull request #14195 from rgacogne/ddist-no-assertions
[thirdparty/pdns.git] / pdns / ednscookies.cc
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "ednscookies.hh"
26 #include "misc.hh"
27
28 #ifdef HAVE_CRYPTO_SHORTHASH
29 #include <sodium.h>
30 #endif
31
32 EDNSCookiesOpt::EDNSCookiesOpt(const std::string& option)
33 {
34 getEDNSCookiesOptFromString(option.c_str(), option.length());
35 }
36
37 EDNSCookiesOpt::EDNSCookiesOpt(const char* option, unsigned int len)
38 {
39 getEDNSCookiesOptFromString(option, len);
40 }
41
42 bool EDNSCookiesOpt::makeFromString(const std::string& option)
43 {
44 getEDNSCookiesOptFromString(option.c_str(), option.length());
45 return isWellFormed();
46 }
47
48 bool EDNSCookiesOpt::makeFromString(const char* option, unsigned int len)
49 {
50 getEDNSCookiesOptFromString(option, len);
51 return isWellFormed();
52 }
53
54 string EDNSCookiesOpt::makeOptString() const
55 {
56 string ret;
57 if (!isWellFormed())
58 return ret;
59 ret.assign(client);
60 if (server.length() != 0)
61 ret.append(server);
62 return ret;
63 }
64
65 void EDNSCookiesOpt::getEDNSCookiesOptFromString(const char* option, unsigned int len)
66 {
67 client.clear();
68 server.clear();
69 if (len < 8)
70 return;
71 client = string(option, 8);
72 if (len > 8) {
73 server = string(option + 8, len - 8);
74 }
75 }
76
77 bool EDNSCookiesOpt::isValid([[maybe_unused]] const string& secret, [[maybe_unused]] const ComboAddress& source) const
78 {
79 #ifdef HAVE_CRYPTO_SHORTHASH
80 if (server.length() != 16 || client.length() != 8) {
81 return false;
82 }
83 if (server[0] != '\x01') {
84 // Version is not 1, can't verify
85 return false;
86 }
87 uint32_t ts;
88 memcpy(&ts, &server[4], sizeof(ts));
89 ts = ntohl(ts);
90 // coverity[store_truncates_time_t]
91 uint32_t now = static_cast<uint32_t>(time(nullptr));
92 // RFC 9018 section 4.3:
93 // The DNS server
94 // SHOULD allow cookies within a 1-hour period in the past and a
95 // 5-minute period into the future
96 if (rfc1982LessThan(now + 300, ts) && rfc1982LessThan(ts + 3600, now)) {
97 return false;
98 }
99 if (secret.length() != crypto_shorthash_KEYBYTES) {
100 return false;
101 }
102
103 string toHash = client + server.substr(0, 8) + source.toByteString();
104 string hashResult;
105 hashResult.resize(8);
106 crypto_shorthash(
107 reinterpret_cast<unsigned char*>(&hashResult[0]),
108 reinterpret_cast<const unsigned char*>(&toHash[0]),
109 toHash.length(),
110 reinterpret_cast<const unsigned char*>(&secret[0]));
111 return constantTimeStringEquals(server.substr(8), hashResult);
112 #else
113 return false;
114 #endif
115 }
116
117 bool EDNSCookiesOpt::shouldRefresh() const
118 {
119 if (server.size() < 16) {
120 return true;
121 }
122 uint32_t ts;
123 memcpy(&ts, &server[4], sizeof(ts));
124 ts = ntohl(ts);
125 // coverity[store_truncates_time_t]
126 uint32_t now = static_cast<uint32_t>(time(nullptr));
127 // RFC 9018 section 4.3:
128 // The DNS server
129 // SHOULD allow cookies within a 1-hour period in the past and a
130 // 5-minute period into the future
131 // If this is not the case, we need to refresh
132 if (rfc1982LessThan(now + 300, ts) && rfc1982LessThan(ts + 3600, now)) {
133 return true;
134 }
135
136 // RFC 9018 section 4.3:
137 // The DNS server SHOULD generate a new Server Cookie at least if the
138 // received Server Cookie from the client is more than half an hour old
139 return rfc1982LessThan(ts + 1800, now);
140 }
141
142 bool EDNSCookiesOpt::makeServerCookie([[maybe_unused]] const string& secret, [[maybe_unused]] const ComboAddress& source)
143 {
144 #ifdef HAVE_CRYPTO_SHORTHASH
145 static_assert(EDNSCookieSecretSize == crypto_shorthash_KEYBYTES * 2, "The EDNSCookieSecretSize is not twice crypto_shorthash_KEYBYTES");
146
147 if (isValid(secret, source) && !shouldRefresh()) {
148 return true;
149 }
150
151 if (secret.length() != crypto_shorthash_KEYBYTES) {
152 return false;
153 }
154
155 server.clear();
156 server.reserve(16);
157 server = "\x01"; // Version
158 server.resize(4, '\0'); // 3 reserved bytes
159 // coverity[store_truncates_time_t]
160 uint32_t now = htonl(static_cast<uint32_t>(time(nullptr)));
161 server += string(reinterpret_cast<const char*>(&now), sizeof(now));
162 server.resize(8);
163
164 string toHash = client;
165 toHash += server;
166 toHash += source.toByteString();
167 server.resize(16);
168 crypto_shorthash(
169 reinterpret_cast<unsigned char*>(&server[8]),
170 reinterpret_cast<const unsigned char*>(&toHash[0]),
171 toHash.length(),
172 reinterpret_cast<const unsigned char*>(&secret[0]));
173 return true;
174 #else
175 return false;
176 #endif
177 }