OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "net/quic/crypto/crypto_server_config.h" | 5 #include "net/quic/crypto/crypto_server_config.h" |
6 | 6 |
7 #include "base/stl_util.h" | 7 #include "base/stl_util.h" |
8 #include "crypto/hkdf.h" | 8 #include "crypto/hkdf.h" |
9 #include "crypto/secure_hash.h" | 9 #include "crypto/secure_hash.h" |
10 #include "net/quic/crypto/aes_128_gcm_decrypter.h" | 10 #include "net/quic/crypto/aes_128_gcm_decrypter.h" |
11 #include "net/quic/crypto/aes_128_gcm_encrypter.h" | 11 #include "net/quic/crypto/aes_128_gcm_encrypter.h" |
12 #include "net/quic/crypto/cert_compressor.h" | 12 #include "net/quic/crypto/cert_compressor.h" |
13 #include "net/quic/crypto/crypto_framer.h" | 13 #include "net/quic/crypto/crypto_framer.h" |
14 #include "net/quic/crypto/crypto_server_config_protobuf.h" | 14 #include "net/quic/crypto/crypto_server_config_protobuf.h" |
15 #include "net/quic/crypto/crypto_utils.h" | 15 #include "net/quic/crypto/crypto_utils.h" |
16 #include "net/quic/crypto/curve25519_key_exchange.h" | 16 #include "net/quic/crypto/curve25519_key_exchange.h" |
| 17 #include "net/quic/crypto/ephemeral_key_source.h" |
17 #include "net/quic/crypto/key_exchange.h" | 18 #include "net/quic/crypto/key_exchange.h" |
18 #include "net/quic/crypto/p256_key_exchange.h" | 19 #include "net/quic/crypto/p256_key_exchange.h" |
19 #include "net/quic/crypto/proof_source.h" | 20 #include "net/quic/crypto/proof_source.h" |
20 #include "net/quic/crypto/quic_decrypter.h" | 21 #include "net/quic/crypto/quic_decrypter.h" |
21 #include "net/quic/crypto/quic_encrypter.h" | 22 #include "net/quic/crypto/quic_encrypter.h" |
22 #include "net/quic/crypto/quic_random.h" | 23 #include "net/quic/crypto/quic_random.h" |
23 #include "net/quic/crypto/source_address_token.h" | 24 #include "net/quic/crypto/source_address_token.h" |
24 #include "net/quic/crypto/strike_register.h" | 25 #include "net/quic/crypto/strike_register.h" |
25 #include "net/quic/quic_clock.h" | 26 #include "net/quic/quic_clock.h" |
26 #include "net/quic/quic_protocol.h" | 27 #include "net/quic/quic_protocol.h" |
(...skipping 18 matching lines...) Expand all Loading... |
45 // probability 0.5 after 2**48 values. We assume that obtaining 2**48 | 46 // probability 0.5 after 2**48 values. We assume that obtaining 2**48 |
46 // source address tokens is not possible: at a rate of 10M packets per | 47 // source address tokens is not possible: at a rate of 10M packets per |
47 // second, it would still take the attacker a year to obtain the needed | 48 // second, it would still take the attacker a year to obtain the needed |
48 // number of packets. | 49 // number of packets. |
49 // | 50 // |
50 // TODO(agl): switch to an encrypter with a larger nonce space (i.e. | 51 // TODO(agl): switch to an encrypter with a larger nonce space (i.e. |
51 // Salsa20+Poly1305). | 52 // Salsa20+Poly1305). |
52 : strike_register_lock_(), | 53 : strike_register_lock_(), |
53 source_address_token_encrypter_(new Aes128GcmEncrypter), | 54 source_address_token_encrypter_(new Aes128GcmEncrypter), |
54 source_address_token_decrypter_(new Aes128GcmDecrypter) { | 55 source_address_token_decrypter_(new Aes128GcmDecrypter) { |
55 crypto::HKDF hkdf(source_address_token_secret, StringPiece() /* no salt */, | 56 crypto::HKDF hkdf(source_address_token_secret, StringPiece() /* no salt */, |
56 "QUIC source address token key", | 57 "QUIC source address token key", |
57 source_address_token_encrypter_->GetKeySize(), | 58 source_address_token_encrypter_->GetKeySize(), |
58 0 /* no fixed IV needed */); | 59 0 /* no fixed IV needed */); |
59 source_address_token_encrypter_->SetKey(hkdf.server_write_key()); | 60 source_address_token_encrypter_->SetKey(hkdf.server_write_key()); |
60 source_address_token_decrypter_->SetKey(hkdf.server_write_key()); | 61 source_address_token_decrypter_->SetKey(hkdf.server_write_key()); |
61 } | 62 } |
62 | 63 |
63 QuicCryptoServerConfig::~QuicCryptoServerConfig() { | 64 QuicCryptoServerConfig::~QuicCryptoServerConfig() { |
64 STLDeleteValues(&configs_); | 65 STLDeleteValues(&configs_); |
65 } | 66 } |
66 | 67 |
67 // static | 68 // static |
68 QuicServerConfigProtobuf* QuicCryptoServerConfig::DefaultConfig( | 69 QuicServerConfigProtobuf* QuicCryptoServerConfig::DefaultConfig( |
69 QuicRandom* rand, | 70 QuicRandom* rand, |
70 const QuicClock* clock, | 71 const QuicClock* clock, |
71 const CryptoHandshakeMessage& extra_tags) { | 72 const CryptoHandshakeMessage& extra_tags, |
| 73 uint64 expiry_time) { |
72 CryptoHandshakeMessage msg; | 74 CryptoHandshakeMessage msg; |
73 | 75 |
74 const string curve25519_private_key = | 76 const string curve25519_private_key = |
75 Curve25519KeyExchange::NewPrivateKey(rand); | 77 Curve25519KeyExchange::NewPrivateKey(rand); |
76 scoped_ptr<Curve25519KeyExchange> curve25519( | 78 scoped_ptr<Curve25519KeyExchange> curve25519( |
77 Curve25519KeyExchange::New(curve25519_private_key)); | 79 Curve25519KeyExchange::New(curve25519_private_key)); |
78 StringPiece curve25519_public_value = curve25519->public_value(); | 80 StringPiece curve25519_public_value = curve25519->public_value(); |
79 | 81 |
80 const string p256_private_key = | 82 const string p256_private_key = P256KeyExchange::NewPrivateKey(); |
81 P256KeyExchange::NewPrivateKey(); | 83 scoped_ptr<P256KeyExchange> p256(P256KeyExchange::New(p256_private_key)); |
82 scoped_ptr<P256KeyExchange> p256( | |
83 P256KeyExchange::New(p256_private_key)); | |
84 StringPiece p256_public_value = p256->public_value(); | 84 StringPiece p256_public_value = p256->public_value(); |
85 | 85 |
86 string encoded_public_values; | 86 string encoded_public_values; |
87 // First two bytes encode the length of the public value. | 87 // First three bytes encode the length of the public value. |
88 encoded_public_values.push_back(curve25519_public_value.size()); | 88 encoded_public_values.push_back(curve25519_public_value.size()); |
89 encoded_public_values.push_back(curve25519_public_value.size() >> 8); | 89 encoded_public_values.push_back(curve25519_public_value.size() >> 8); |
| 90 encoded_public_values.push_back(curve25519_public_value.size() >> 16); |
90 encoded_public_values.append(curve25519_public_value.data(), | 91 encoded_public_values.append(curve25519_public_value.data(), |
91 curve25519_public_value.size()); | 92 curve25519_public_value.size()); |
92 encoded_public_values.push_back(p256_public_value.size()); | 93 encoded_public_values.push_back(p256_public_value.size()); |
93 encoded_public_values.push_back(p256_public_value.size() >> 8); | 94 encoded_public_values.push_back(p256_public_value.size() >> 8); |
| 95 encoded_public_values.push_back(p256_public_value.size() >> 16); |
94 encoded_public_values.append(p256_public_value.data(), | 96 encoded_public_values.append(p256_public_value.data(), |
95 p256_public_value.size()); | 97 p256_public_value.size()); |
96 | 98 |
97 msg.set_tag(kSCFG); | 99 msg.set_tag(kSCFG); |
98 msg.SetTaglist(kKEXS, kC255, kP256, 0); | 100 msg.SetTaglist(kKEXS, kC255, kP256, 0); |
99 msg.SetTaglist(kAEAD, kAESG, 0); | 101 msg.SetTaglist(kAEAD, kAESG, 0); |
100 msg.SetValue(kVERS, static_cast<uint16>(0)); | 102 msg.SetValue(kVERS, static_cast<uint16>(0)); |
101 msg.SetStringPiece(kPUBS, encoded_public_values); | 103 msg.SetStringPiece(kPUBS, encoded_public_values); |
102 msg.Insert(extra_tags.tag_value_map().begin(), | 104 msg.Insert(extra_tags.tag_value_map().begin(), |
103 extra_tags.tag_value_map().end()); | 105 extra_tags.tag_value_map().end()); |
104 | 106 |
| 107 if (expiry_time == 0) { |
| 108 const QuicWallTime now = clock->WallNow(); |
| 109 const QuicWallTime expiry = now.Add(QuicTime::Delta::FromSeconds( |
| 110 60 * 60 * 24 * 180 /* 180 days, ~six months */)); |
| 111 const uint64 expiry_seconds = expiry.ToUNIXSeconds(); |
| 112 msg.SetValue(kEXPY, expiry_seconds); |
| 113 } else { |
| 114 msg.SetValue(kEXPY, expiry_time); |
| 115 } |
| 116 |
105 char scid_bytes[16]; | 117 char scid_bytes[16]; |
106 rand->RandBytes(scid_bytes, sizeof(scid_bytes)); | 118 rand->RandBytes(scid_bytes, sizeof(scid_bytes)); |
107 msg.SetStringPiece(kSCID, StringPiece(scid_bytes, sizeof(scid_bytes))); | 119 msg.SetStringPiece(kSCID, StringPiece(scid_bytes, sizeof(scid_bytes))); |
108 | 120 |
109 char orbit_bytes[kOrbitSize]; | 121 char orbit_bytes[kOrbitSize]; |
110 rand->RandBytes(orbit_bytes, sizeof(orbit_bytes)); | 122 rand->RandBytes(orbit_bytes, sizeof(orbit_bytes)); |
111 msg.SetStringPiece(kORBT, StringPiece(orbit_bytes, sizeof(orbit_bytes))); | 123 msg.SetStringPiece(kORBT, StringPiece(orbit_bytes, sizeof(orbit_bytes))); |
112 | 124 |
113 scoped_ptr<QuicData> serialized( | 125 scoped_ptr<QuicData> serialized(CryptoFramer::ConstructHandshakeMessage(msg)); |
114 CryptoFramer::ConstructHandshakeMessage(msg)); | |
115 | 126 |
116 scoped_ptr<QuicServerConfigProtobuf> config(new QuicServerConfigProtobuf); | 127 scoped_ptr<QuicServerConfigProtobuf> config(new QuicServerConfigProtobuf); |
117 config->set_config(serialized->AsStringPiece()); | 128 config->set_config(serialized->AsStringPiece()); |
118 QuicServerConfigProtobuf::PrivateKey* curve25519_key = config->add_key(); | 129 QuicServerConfigProtobuf::PrivateKey* curve25519_key = config->add_key(); |
119 curve25519_key->set_tag(kC255); | 130 curve25519_key->set_tag(kC255); |
120 curve25519_key->set_private_key(curve25519_private_key); | 131 curve25519_key->set_private_key(curve25519_private_key); |
121 QuicServerConfigProtobuf::PrivateKey* p256_key = config->add_key(); | 132 QuicServerConfigProtobuf::PrivateKey* p256_key = config->add_key(); |
122 p256_key->set_tag(kP256); | 133 p256_key->set_tag(kP256); |
123 p256_key->set_private_key(p256_private_key); | 134 p256_key->set_private_key(p256_private_key); |
124 | 135 |
125 return config.release(); | 136 return config.release(); |
126 } | 137 } |
127 | 138 |
128 CryptoHandshakeMessage* QuicCryptoServerConfig::AddConfig( | 139 CryptoHandshakeMessage* QuicCryptoServerConfig::AddConfig( |
129 QuicServerConfigProtobuf* protobuf) { | 140 QuicServerConfigProtobuf* protobuf) { |
130 scoped_ptr<CryptoHandshakeMessage> msg( | 141 scoped_ptr<CryptoHandshakeMessage> msg( |
131 CryptoFramer::ParseMessage(protobuf->config())); | 142 CryptoFramer::ParseMessage(protobuf->config())); |
132 | 143 |
133 if (!msg.get()) { | 144 if (!msg.get()) { |
134 LOG(WARNING) << "Failed to parse server config message"; | 145 LOG(WARNING) << "Failed to parse server config message"; |
135 return NULL; | 146 return NULL; |
136 } | 147 } |
137 if (msg->tag() != kSCFG) { | 148 if (msg->tag() != kSCFG) { |
138 LOG(WARNING) << "Server config message has tag " | 149 LOG(WARNING) << "Server config message has tag " << msg->tag() |
139 << msg->tag() << " expected " | 150 << " expected " << kSCFG; |
140 << kSCFG; | |
141 return NULL; | 151 return NULL; |
142 } | 152 } |
143 | 153 |
144 scoped_ptr<Config> config(new Config); | 154 scoped_ptr<Config> config(new Config); |
145 config->serialized = protobuf->config(); | 155 config->serialized = protobuf->config(); |
146 | 156 |
147 StringPiece scid; | 157 StringPiece scid; |
148 if (!msg->GetStringPiece(kSCID, &scid)) { | 158 if (!msg->GetStringPiece(kSCID, &scid)) { |
149 LOG(WARNING) << "Server config message is missing SCID"; | 159 LOG(WARNING) << "Server config message is missing SCID"; |
150 return NULL; | 160 return NULL; |
151 } | 161 } |
152 config->id = scid.as_string(); | 162 config->id = scid.as_string(); |
153 | 163 |
154 const CryptoTag* aead_tags; | 164 const QuicTag* aead_tags; |
155 size_t aead_len; | 165 size_t aead_len; |
156 if (msg->GetTaglist(kAEAD, &aead_tags, &aead_len) != QUIC_NO_ERROR) { | 166 if (msg->GetTaglist(kAEAD, &aead_tags, &aead_len) != QUIC_NO_ERROR) { |
157 LOG(WARNING) << "Server config message is missing AEAD"; | 167 LOG(WARNING) << "Server config message is missing AEAD"; |
158 return NULL; | 168 return NULL; |
159 } | 169 } |
160 config->aead = vector<CryptoTag>(aead_tags, aead_tags + aead_len); | 170 config->aead = vector<QuicTag>(aead_tags, aead_tags + aead_len); |
161 | 171 |
162 const CryptoTag* kexs_tags; | 172 const QuicTag* kexs_tags; |
163 size_t kexs_len; | 173 size_t kexs_len; |
164 if (msg->GetTaglist(kKEXS, &kexs_tags, &kexs_len) != QUIC_NO_ERROR) { | 174 if (msg->GetTaglist(kKEXS, &kexs_tags, &kexs_len) != QUIC_NO_ERROR) { |
165 LOG(WARNING) << "Server config message is missing KEXS"; | 175 LOG(WARNING) << "Server config message is missing KEXS"; |
166 return NULL; | 176 return NULL; |
167 } | 177 } |
168 | 178 |
169 StringPiece orbit; | 179 StringPiece orbit; |
170 if (!msg->GetStringPiece(kORBT, &orbit)) { | 180 if (!msg->GetStringPiece(kORBT, &orbit)) { |
171 LOG(WARNING) << "Server config message is missing OBIT"; | 181 LOG(WARNING) << "Server config message is missing OBIT"; |
172 return NULL; | 182 return NULL; |
173 } | 183 } |
174 | 184 |
175 if (orbit.size() != kOrbitSize) { | 185 if (orbit.size() != kOrbitSize) { |
176 LOG(WARNING) << "Orbit value in server config is the wrong length." | 186 LOG(WARNING) << "Orbit value in server config is the wrong length." |
177 " Got " << orbit.size() << " want " << kOrbitSize; | 187 " Got " << orbit.size() << " want " << kOrbitSize; |
178 return NULL; | 188 return NULL; |
179 } | 189 } |
180 COMPILE_ASSERT(sizeof(config->orbit) == kOrbitSize, orbit_incorrect_size); | 190 COMPILE_ASSERT(sizeof(config->orbit) == kOrbitSize, orbit_incorrect_size); |
181 memcpy(config->orbit, orbit.data(), sizeof(config->orbit)); | 191 memcpy(config->orbit, orbit.data(), sizeof(config->orbit)); |
182 | 192 |
183 if (kexs_len != protobuf->key_size()) { | 193 if (kexs_len != protobuf->key_size()) { |
184 LOG(WARNING) << "Server config has " | 194 LOG(WARNING) << "Server config has " << kexs_len |
185 << kexs_len | |
186 << " key exchange methods configured, but " | 195 << " key exchange methods configured, but " |
187 << protobuf->key_size() | 196 << protobuf->key_size() << " private keys"; |
188 << " private keys"; | |
189 return NULL; | 197 return NULL; |
190 } | 198 } |
191 | 199 |
192 for (size_t i = 0; i < kexs_len; i++) { | 200 for (size_t i = 0; i < kexs_len; i++) { |
193 const CryptoTag tag = kexs_tags[i]; | 201 const QuicTag tag = kexs_tags[i]; |
194 string private_key; | 202 string private_key; |
195 | 203 |
196 config->kexs.push_back(tag); | 204 config->kexs.push_back(tag); |
197 | 205 |
198 for (size_t j = 0; j < protobuf->key_size(); j++) { | 206 for (size_t j = 0; j < protobuf->key_size(); j++) { |
199 const QuicServerConfigProtobuf::PrivateKey& key = protobuf->key(i); | 207 const QuicServerConfigProtobuf::PrivateKey& key = protobuf->key(i); |
200 if (key.tag() == tag) { | 208 if (key.tag() == tag) { |
201 private_key = key.private_key(); | 209 private_key = key.private_key(); |
202 break; | 210 break; |
203 } | 211 } |
204 } | 212 } |
205 | 213 |
206 if (private_key.empty()) { | 214 if (private_key.empty()) { |
207 LOG(WARNING) << "Server config contains key exchange method without " | 215 LOG(WARNING) << "Server config contains key exchange method without " |
208 "corresponding private key: " | 216 "corresponding private key: " << tag; |
209 << tag; | |
210 return NULL; | 217 return NULL; |
211 } | 218 } |
212 | 219 |
213 scoped_ptr<KeyExchange> ka; | 220 scoped_ptr<KeyExchange> ka; |
214 switch (tag) { | 221 switch (tag) { |
215 case kC255: | 222 case kC255: |
216 ka.reset(Curve25519KeyExchange::New(private_key)); | 223 ka.reset(Curve25519KeyExchange::New(private_key)); |
217 if (!ka.get()) { | 224 if (!ka.get()) { |
218 LOG(WARNING) << "Server config contained an invalid curve25519" | 225 LOG(WARNING) << "Server config contained an invalid curve25519" |
219 " private key."; | 226 " private key."; |
| 227 return NULL; |
| 228 } |
| 229 break; |
| 230 case kP256: |
| 231 ka.reset(P256KeyExchange::New(private_key)); |
| 232 if (!ka.get()) { |
| 233 LOG(WARNING) << "Server config contained an invalid P-256" |
| 234 " private key."; |
| 235 return NULL; |
| 236 } |
| 237 break; |
| 238 default: |
| 239 LOG(WARNING) << "Server config message contains unknown key exchange " |
| 240 "method: " << tag; |
220 return NULL; | 241 return NULL; |
221 } | |
222 break; | |
223 case kP256: | |
224 ka.reset(P256KeyExchange::New(private_key)); | |
225 if (!ka.get()) { | |
226 LOG(WARNING) << "Server config contained an invalid P-256" | |
227 " private key."; | |
228 return NULL; | |
229 } | |
230 break; | |
231 default: | |
232 LOG(WARNING) << "Server config message contains unknown key exchange " | |
233 "method: " | |
234 << tag; | |
235 return NULL; | |
236 } | 242 } |
237 | 243 |
238 for (vector<KeyExchange*>::const_iterator i = config->key_exchanges.begin(); | 244 for (vector<KeyExchange*>::const_iterator i = config->key_exchanges.begin(); |
239 i != config->key_exchanges.end(); ++i) { | 245 i != config->key_exchanges.end(); ++i) { |
240 if ((*i)->tag() == tag) { | 246 if ((*i)->tag() == tag) { |
241 LOG(WARNING) << "Duplicate key exchange in config: " << tag; | 247 LOG(WARNING) << "Duplicate key exchange in config: " << tag; |
242 return NULL; | 248 return NULL; |
243 } | 249 } |
244 } | 250 } |
245 | 251 |
(...skipping 18 matching lines...) Expand all Loading... |
264 | 270 |
265 configs_[id] = config.release(); | 271 configs_[id] = config.release(); |
266 active_config_ = id; | 272 active_config_ = id; |
267 | 273 |
268 return msg.release(); | 274 return msg.release(); |
269 } | 275 } |
270 | 276 |
271 CryptoHandshakeMessage* QuicCryptoServerConfig::AddDefaultConfig( | 277 CryptoHandshakeMessage* QuicCryptoServerConfig::AddDefaultConfig( |
272 QuicRandom* rand, | 278 QuicRandom* rand, |
273 const QuicClock* clock, | 279 const QuicClock* clock, |
274 const CryptoHandshakeMessage& extra_tags) { | 280 const CryptoHandshakeMessage& extra_tags, |
| 281 uint64 expiry_time) { |
275 scoped_ptr<QuicServerConfigProtobuf> config(DefaultConfig( | 282 scoped_ptr<QuicServerConfigProtobuf> config(DefaultConfig( |
276 rand, clock, extra_tags)); | 283 rand, clock, extra_tags, expiry_time)); |
277 return AddConfig(config.get()); | 284 return AddConfig(config.get()); |
278 } | 285 } |
279 | 286 |
280 QuicErrorCode QuicCryptoServerConfig::ProcessClientHello( | 287 QuicErrorCode QuicCryptoServerConfig::ProcessClientHello( |
281 const CryptoHandshakeMessage& client_hello, | 288 const CryptoHandshakeMessage& client_hello, |
282 QuicGuid guid, | 289 QuicGuid guid, |
283 const IPEndPoint& client_ip, | 290 const IPEndPoint& client_ip, |
284 QuicTime::Delta now_since_unix_epoch, | 291 const QuicClock* clock, |
285 QuicRandom* rand, | 292 QuicRandom* rand, |
286 QuicCryptoNegotiatedParameters *params, | 293 QuicCryptoNegotiatedParameters *params, |
287 CryptoHandshakeMessage* out, | 294 CryptoHandshakeMessage* out, |
288 string* error_details) const { | 295 string* error_details) const { |
289 DCHECK(error_details); | 296 DCHECK(error_details); |
290 | 297 |
291 CHECK(!configs_.empty()); | 298 CHECK(!configs_.empty()); |
292 | 299 |
293 // FIXME(agl): we should use the client's SCID, not just the active config. | 300 // FIXME(agl): we should use the client's SCID, not just the active config. |
294 map<ServerConfigID, Config*>::const_iterator it = | 301 map<ServerConfigID, Config*>::const_iterator it = |
295 configs_.find(active_config_); | 302 configs_.find(active_config_); |
296 if (it == configs_.end()) { | 303 if (it == configs_.end()) { |
297 *error_details = "No valid server config loaded"; | 304 *error_details = "No valid server config loaded"; |
298 return QUIC_CRYPTO_INTERNAL_ERROR; | 305 return QUIC_CRYPTO_INTERNAL_ERROR; |
299 } | 306 } |
300 const Config* const config(it->second); | 307 const Config* const config(it->second); |
301 | 308 |
| 309 const QuicWallTime now = clock->WallNow(); |
302 bool valid_source_address_token = false; | 310 bool valid_source_address_token = false; |
303 StringPiece srct; | 311 StringPiece srct; |
304 if (client_hello.GetStringPiece(kSRCT, &srct) && | 312 if (client_hello.GetStringPiece(kSourceAddressTokenTag, &srct) && |
305 ValidateSourceAddressToken(srct, client_ip, now_since_unix_epoch)) { | 313 ValidateSourceAddressToken(srct, client_ip, now)) { |
306 valid_source_address_token = true; | 314 valid_source_address_token = true; |
307 } | 315 } |
308 | 316 |
309 const string fresh_source_address_token = | 317 const string fresh_source_address_token = |
310 NewSourceAddressToken(client_ip, rand, now_since_unix_epoch); | 318 NewSourceAddressToken(client_ip, rand, now); |
311 | 319 |
312 // If we previously sent a REJ to this client then we may have stored a | 320 // If we previously sent a REJ to this client then we may have stored a |
313 // server nonce in |params|. In which case, we know that the connection | 321 // server nonce in |params|. In which case, we know that the connection |
314 // is unique because the server nonce will be mixed into the key generation. | 322 // is unique because the server nonce will be mixed into the key generation. |
315 bool unique_by_server_nonce = !params->server_nonce.empty(); | 323 bool unique_by_server_nonce = !params->server_nonce.empty(); |
316 // If we can't ensure uniqueness by a server nonce, then we will try and use | 324 // If we can't ensure uniqueness by a server nonce, then we will try and use |
317 // the strike register. | 325 // the strike register. |
318 bool unique_by_strike_register = false; | 326 bool unique_by_strike_register = false; |
319 | 327 |
320 StringPiece client_nonce; | 328 StringPiece client_nonce; |
321 bool client_nonce_well_formed = false; | 329 bool client_nonce_well_formed = false; |
322 if (client_hello.GetStringPiece(kNONC, &client_nonce) && | 330 if (client_hello.GetStringPiece(kNONC, &client_nonce) && |
323 client_nonce.size() == kNonceSize) { | 331 client_nonce.size() == kNonceSize) { |
324 client_nonce_well_formed = true; | 332 client_nonce_well_formed = true; |
325 base::AutoLock auto_lock(strike_register_lock_); | 333 base::AutoLock auto_lock(strike_register_lock_); |
326 | 334 |
327 if (strike_register_.get() == NULL) { | 335 if (strike_register_.get() == NULL) { |
328 strike_register_.reset(new StrikeRegister( | 336 strike_register_.reset(new StrikeRegister( |
329 // TODO(agl): these magic numbers should come from config. | 337 // TODO(agl): these magic numbers should come from config. |
330 1024 /* max entries */, | 338 1024 /* max entries */, |
331 static_cast<uint32>(now_since_unix_epoch.ToSeconds()), | 339 static_cast<uint32>(now.ToUNIXSeconds()), |
332 600 /* window secs */, config->orbit)); | 340 600 /* window secs */, config->orbit)); |
333 } | 341 } |
334 unique_by_strike_register = strike_register_->Insert( | 342 unique_by_strike_register = strike_register_->Insert( |
335 reinterpret_cast<const uint8*>(client_nonce.data()), | 343 reinterpret_cast<const uint8*>(client_nonce.data()), |
336 static_cast<uint32>(now_since_unix_epoch.ToSeconds())); | 344 static_cast<uint32>(now.ToUNIXSeconds())); |
337 } | 345 } |
338 | 346 |
| 347 StringPiece server_nonce; |
| 348 client_hello.GetStringPiece(kServerNonceTag, &server_nonce); |
| 349 const bool server_nonce_matches = server_nonce == params->server_nonce; |
| 350 |
339 out->Clear(); | 351 out->Clear(); |
340 | 352 |
341 StringPiece sni; | 353 StringPiece sni; |
342 client_hello.GetStringPiece(kSNI, &sni); | 354 client_hello.GetStringPiece(kSNI, &sni); |
343 | 355 |
344 StringPiece scid; | 356 StringPiece scid; |
345 if (!client_hello.GetStringPiece(kSCID, &scid) || | 357 if (!client_hello.GetStringPiece(kSCID, &scid) || |
346 scid.as_string() != config->id || | 358 scid.as_string() != config->id || |
347 !valid_source_address_token || | 359 !valid_source_address_token || |
348 !client_nonce_well_formed || | 360 !client_nonce_well_formed || |
| 361 !server_nonce_matches || |
349 (!unique_by_strike_register && | 362 (!unique_by_strike_register && |
350 !unique_by_server_nonce)) { | 363 !unique_by_server_nonce)) { |
351 // If the client didn't provide a server config ID, or gave the wrong one, | 364 // If the client didn't provide a server config ID, or gave the wrong one, |
352 // then the handshake cannot possibly complete. We reject the handshake and | 365 // then the handshake cannot possibly complete. We reject the handshake and |
353 // give the client enough information to do better next time. | 366 // give the client enough information to do better next time. |
354 out->set_tag(kREJ); | 367 out->set_tag(kREJ); |
355 out->SetStringPiece(kSCFG, config->serialized); | 368 out->SetStringPiece(kSCFG, config->serialized); |
356 out->SetStringPiece(kSRCT, fresh_source_address_token); | 369 out->SetStringPiece(kSourceAddressTokenTag, fresh_source_address_token); |
357 if (params->server_nonce.empty()) { | 370 if (params->server_nonce.empty()) { |
358 CryptoUtils::GenerateNonce( | 371 CryptoUtils::GenerateNonce( |
359 now_since_unix_epoch, rand, | 372 now, rand, StringPiece(reinterpret_cast<const char*>(config->orbit), |
360 StringPiece(reinterpret_cast<const char*>(config->orbit), | 373 sizeof(config->orbit)), |
361 sizeof(config->orbit)), | |
362 ¶ms->server_nonce); | 374 ¶ms->server_nonce); |
363 } | 375 } |
364 out->SetStringPiece(kNONC, params->server_nonce); | 376 out->SetStringPiece(kServerNonceTag, params->server_nonce); |
365 | 377 |
366 // The client may have requested a certificate chain. | 378 // The client may have requested a certificate chain. |
367 const CryptoTag* their_proof_demands; | 379 const QuicTag* their_proof_demands; |
368 size_t num_their_proof_demands; | 380 size_t num_their_proof_demands; |
369 | 381 |
370 if (proof_source_.get() != NULL && | 382 if (proof_source_.get() != NULL && !sni.empty() && |
371 !sni.empty() && | |
372 client_hello.GetTaglist(kPDMD, &their_proof_demands, | 383 client_hello.GetTaglist(kPDMD, &their_proof_demands, |
373 &num_their_proof_demands) == QUIC_NO_ERROR) { | 384 &num_their_proof_demands) == QUIC_NO_ERROR) { |
374 for (size_t i = 0; i < num_their_proof_demands; i++) { | 385 for (size_t i = 0; i < num_their_proof_demands; i++) { |
375 if (their_proof_demands[i] != kX509) { | 386 if (their_proof_demands[i] != kX509) { |
376 continue; | 387 continue; |
377 } | 388 } |
378 | 389 |
379 const vector<string>* certs; | 390 const vector<string>* certs; |
380 string signature; | 391 string signature; |
381 if (!proof_source_->GetProof(sni.as_string(), config->serialized, | 392 if (!proof_source_->GetProof(sni.as_string(), config->serialized, |
382 &certs, &signature)) { | 393 &certs, &signature)) { |
383 break; | 394 break; |
384 } | 395 } |
385 | 396 |
386 StringPiece their_common_set_hashes; | 397 StringPiece their_common_set_hashes; |
387 StringPiece their_cached_cert_hashes; | 398 StringPiece their_cached_cert_hashes; |
388 client_hello.GetStringPiece(kCCS, &their_common_set_hashes); | 399 client_hello.GetStringPiece(kCCS, &their_common_set_hashes); |
389 client_hello.GetStringPiece(kCCRT, &their_cached_cert_hashes); | 400 client_hello.GetStringPiece(kCCRT, &their_cached_cert_hashes); |
390 | 401 |
391 const string compressed = CertCompressor::CompressChain( | 402 const string compressed = CertCompressor::CompressChain( |
392 *certs, their_common_set_hashes, their_cached_cert_hashes, | 403 *certs, their_common_set_hashes, their_cached_cert_hashes, |
393 config->common_cert_set_.get()); | 404 config->common_cert_set_.get()); |
394 | 405 |
395 // kMaxUnverifiedSize is the number of bytes that the certificate chain | 406 // kMaxUnverifiedSize is the number of bytes that the certificate chain |
396 // and signature can consume before we will demand a valid | 407 // and signature can consume before we will demand a valid |
397 // source-address token. | 408 // source-address token. |
398 static const size_t kMaxUnverifiedSize = 400; | 409 static const size_t kMaxUnverifiedSize = 400; |
399 if (valid_source_address_token || | 410 if (valid_source_address_token || |
400 signature.size() + compressed.size() < kMaxUnverifiedSize) { | 411 signature.size() + compressed.size() < kMaxUnverifiedSize) { |
401 out->SetStringPiece(kCERT, compressed); | 412 out->SetStringPiece(kCertificateTag, compressed); |
402 out->SetStringPiece(kPROF, signature); | 413 out->SetStringPiece(kPROF, signature); |
403 } | 414 } |
404 break; | 415 break; |
405 } | 416 } |
406 } | 417 } |
407 | 418 |
408 return QUIC_NO_ERROR; | 419 return QUIC_NO_ERROR; |
409 } | 420 } |
410 | 421 |
411 const CryptoTag* their_aeads; | 422 const QuicTag* their_aeads; |
412 const CryptoTag* their_key_exchanges; | 423 const QuicTag* their_key_exchanges; |
413 size_t num_their_aeads, num_their_key_exchanges; | 424 size_t num_their_aeads, num_their_key_exchanges; |
414 if (client_hello.GetTaglist(kAEAD, &their_aeads, | 425 if (client_hello.GetTaglist(kAEAD, &their_aeads, |
415 &num_their_aeads) != QUIC_NO_ERROR || | 426 &num_their_aeads) != QUIC_NO_ERROR || |
416 client_hello.GetTaglist(kKEXS, &their_key_exchanges, | 427 client_hello.GetTaglist(kKEXS, &their_key_exchanges, |
417 &num_their_key_exchanges) != QUIC_NO_ERROR || | 428 &num_their_key_exchanges) != QUIC_NO_ERROR || |
418 num_their_aeads != 1 || | 429 num_their_aeads != 1 || |
419 num_their_key_exchanges != 1) { | 430 num_their_key_exchanges != 1) { |
420 *error_details = "Missing or invalid AEAD or KEXS"; | 431 *error_details = "Missing or invalid AEAD or KEXS"; |
421 return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; | 432 return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
422 } | 433 } |
423 | 434 |
424 size_t key_exchange_index; | 435 size_t key_exchange_index; |
425 if (!CryptoUtils::FindMutualTag(config->aead, | 436 if (!CryptoUtils::FindMutualTag(config->aead, their_aeads, num_their_aeads, |
426 their_aeads, num_their_aeads, | 437 CryptoUtils::LOCAL_PRIORITY, ¶ms->aead, |
427 CryptoUtils::LOCAL_PRIORITY, | |
428 ¶ms->aead, | |
429 NULL) || | 438 NULL) || |
430 !CryptoUtils::FindMutualTag(config->kexs, | 439 !CryptoUtils::FindMutualTag( |
431 their_key_exchanges, num_their_key_exchanges, | 440 config->kexs, their_key_exchanges, num_their_key_exchanges, |
432 CryptoUtils::LOCAL_PRIORITY, | 441 CryptoUtils::LOCAL_PRIORITY, ¶ms->key_exchange, |
433 ¶ms->key_exchange, | 442 &key_exchange_index)) { |
434 &key_exchange_index)) { | |
435 *error_details = "Unsupported AEAD or KEXS"; | 443 *error_details = "Unsupported AEAD or KEXS"; |
436 return QUIC_CRYPTO_NO_SUPPORT; | 444 return QUIC_CRYPTO_NO_SUPPORT; |
437 } | 445 } |
438 | 446 |
439 StringPiece public_value; | 447 StringPiece public_value; |
440 if (!client_hello.GetStringPiece(kPUBS, &public_value)) { | 448 if (!client_hello.GetStringPiece(kPUBS, &public_value)) { |
441 *error_details = "Missing public value"; | 449 *error_details = "Missing public value"; |
442 return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; | 450 return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
443 } | 451 } |
444 | 452 |
445 if (!config->key_exchanges[key_exchange_index]->CalculateSharedKey( | 453 const KeyExchange* key_exchange = config->key_exchanges[key_exchange_index]; |
446 public_value, ¶ms->premaster_secret)) { | 454 if (!key_exchange->CalculateSharedKey(public_value, |
| 455 ¶ms->initial_premaster_secret)) { |
447 *error_details = "Invalid public value"; | 456 *error_details = "Invalid public value"; |
448 return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; | 457 return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
449 } | 458 } |
450 | 459 |
451 params->server_config_id = scid.as_string(); | 460 params->server_config_id = scid.as_string(); |
452 | 461 |
453 string hkdf_input(QuicCryptoConfig::kLabel, | 462 string hkdf_suffix; |
454 strlen(QuicCryptoConfig::kLabel) + 1); | 463 const QuicData& client_hello_serialized = client_hello.GetSerialized(); |
455 hkdf_input.append(reinterpret_cast<char*>(&guid), sizeof(guid)); | 464 hkdf_suffix.reserve(sizeof(guid) + client_hello_serialized.length() + |
| 465 config->serialized.size()); |
| 466 hkdf_suffix.append(reinterpret_cast<char*>(&guid), sizeof(guid)); |
| 467 hkdf_suffix.append(client_hello_serialized.data(), |
| 468 client_hello_serialized.length()); |
| 469 hkdf_suffix.append(config->serialized); |
456 | 470 |
457 const QuicData& client_hello_serialized = client_hello.GetSerialized(); | 471 string hkdf_input; |
458 hkdf_input.append(client_hello_serialized.data(), | 472 size_t label_len = strlen(QuicCryptoConfig::kInitialLabel) + 1; |
459 client_hello_serialized.length()); | 473 hkdf_input.reserve(label_len + hkdf_suffix.size()); |
460 hkdf_input.append(config->serialized); | 474 hkdf_input.append(QuicCryptoConfig::kInitialLabel, label_len); |
| 475 hkdf_input.append(hkdf_suffix); |
461 | 476 |
462 CryptoUtils::DeriveKeys(params, client_nonce, hkdf_input, | 477 CryptoUtils::DeriveKeys(params->initial_premaster_secret, params->aead, |
463 CryptoUtils::SERVER); | 478 client_nonce, params->server_nonce, hkdf_input, |
| 479 CryptoUtils::SERVER, ¶ms->initial_crypters); |
| 480 |
| 481 string forward_secure_public_value; |
| 482 if (ephemeral_key_source_.get()) { |
| 483 params->forward_secure_premaster_secret = |
| 484 ephemeral_key_source_->CalculateForwardSecureKey( |
| 485 key_exchange, rand, clock->ApproximateNow(), public_value, |
| 486 &forward_secure_public_value); |
| 487 } else { |
| 488 scoped_ptr<KeyExchange> forward_secure_key_exchange( |
| 489 key_exchange->NewKeyPair(rand)); |
| 490 forward_secure_public_value = |
| 491 forward_secure_key_exchange->public_value().as_string(); |
| 492 if (!forward_secure_key_exchange->CalculateSharedKey( |
| 493 public_value, ¶ms->forward_secure_premaster_secret)) { |
| 494 *error_details = "Invalid public value"; |
| 495 return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; |
| 496 } |
| 497 } |
| 498 |
| 499 string forward_secure_hkdf_input; |
| 500 label_len = strlen(QuicCryptoConfig::kForwardSecureLabel) + 1; |
| 501 forward_secure_hkdf_input.reserve(label_len + hkdf_suffix.size()); |
| 502 forward_secure_hkdf_input.append(QuicCryptoConfig::kForwardSecureLabel, |
| 503 label_len); |
| 504 forward_secure_hkdf_input.append(hkdf_suffix); |
| 505 |
| 506 CryptoUtils::DeriveKeys(params->forward_secure_premaster_secret, params->aead, |
| 507 client_nonce, params->server_nonce, |
| 508 forward_secure_hkdf_input, CryptoUtils::SERVER, |
| 509 ¶ms->forward_secure_crypters); |
464 | 510 |
465 out->set_tag(kSHLO); | 511 out->set_tag(kSHLO); |
466 out->SetStringPiece(kSRCT, fresh_source_address_token); | 512 out->SetStringPiece(kSourceAddressTokenTag, fresh_source_address_token); |
| 513 out->SetStringPiece(kPUBS, forward_secure_public_value); |
467 return QUIC_NO_ERROR; | 514 return QUIC_NO_ERROR; |
468 } | 515 } |
469 | 516 |
470 void QuicCryptoServerConfig::SetProofSource(ProofSource* proof_source) { | 517 void QuicCryptoServerConfig::SetProofSource(ProofSource* proof_source) { |
471 proof_source_.reset(proof_source); | 518 proof_source_.reset(proof_source); |
472 } | 519 } |
473 | 520 |
| 521 void QuicCryptoServerConfig::SetEphemeralKeySource( |
| 522 EphemeralKeySource* ephemeral_key_source) { |
| 523 ephemeral_key_source_.reset(ephemeral_key_source); |
| 524 } |
| 525 |
474 string QuicCryptoServerConfig::NewSourceAddressToken( | 526 string QuicCryptoServerConfig::NewSourceAddressToken( |
475 const IPEndPoint& ip, | 527 const IPEndPoint& ip, |
476 QuicRandom* rand, | 528 QuicRandom* rand, |
477 QuicTime::Delta now_since_epoch) const { | 529 QuicWallTime now) const { |
478 SourceAddressToken source_address_token; | 530 SourceAddressToken source_address_token; |
479 source_address_token.set_ip(ip.ToString()); | 531 source_address_token.set_ip(ip.ToString()); |
480 source_address_token.set_timestamp(now_since_epoch.ToSeconds()); | 532 source_address_token.set_timestamp(now.ToUNIXSeconds()); |
481 | 533 |
482 string plaintext = source_address_token.SerializeAsString(); | 534 string plaintext = source_address_token.SerializeAsString(); |
483 char nonce[12]; | 535 char nonce[12]; |
484 DCHECK_EQ(sizeof(nonce), | 536 DCHECK_EQ(sizeof(nonce), |
485 source_address_token_encrypter_->GetNoncePrefixSize() + | 537 source_address_token_encrypter_->GetNoncePrefixSize() + |
486 sizeof(QuicPacketSequenceNumber)); | 538 sizeof(QuicPacketSequenceNumber)); |
487 rand->RandBytes(nonce, sizeof(nonce)); | 539 rand->RandBytes(nonce, sizeof(nonce)); |
488 | 540 |
489 size_t ciphertext_size = | 541 size_t ciphertext_size = |
490 source_address_token_encrypter_->GetCiphertextSize(plaintext.size()); | 542 source_address_token_encrypter_->GetCiphertextSize(plaintext.size()); |
491 string result; | 543 string result; |
492 result.resize(sizeof(nonce) + ciphertext_size); | 544 result.resize(sizeof(nonce) + ciphertext_size); |
493 memcpy(&result[0], &nonce, sizeof(nonce)); | 545 memcpy(&result[0], &nonce, sizeof(nonce)); |
494 | 546 |
495 if (!source_address_token_encrypter_->Encrypt( | 547 if (!source_address_token_encrypter_->Encrypt( |
496 StringPiece(nonce, sizeof(nonce)), StringPiece(), plaintext, | 548 StringPiece(nonce, sizeof(nonce)), StringPiece(), plaintext, |
497 reinterpret_cast<unsigned char*>(&result[sizeof(nonce)]))) { | 549 reinterpret_cast<unsigned char*>(&result[sizeof(nonce)]))) { |
498 DCHECK(false); | 550 DCHECK(false); |
499 return string(); | 551 return string(); |
500 } | 552 } |
501 | 553 |
502 return result; | 554 return result; |
503 } | 555 } |
504 | 556 |
505 bool QuicCryptoServerConfig::ValidateSourceAddressToken( | 557 bool QuicCryptoServerConfig::ValidateSourceAddressToken( |
506 StringPiece token, | 558 StringPiece token, |
507 const IPEndPoint& ip, | 559 const IPEndPoint& ip, |
508 QuicTime::Delta now_since_epoch) const { | 560 QuicWallTime now) const { |
509 char nonce[12]; | 561 char nonce[12]; |
510 DCHECK_EQ(sizeof(nonce), | 562 DCHECK_EQ(sizeof(nonce), |
511 source_address_token_encrypter_->GetNoncePrefixSize() + | 563 source_address_token_encrypter_->GetNoncePrefixSize() + |
512 sizeof(QuicPacketSequenceNumber)); | 564 sizeof(QuicPacketSequenceNumber)); |
513 | 565 |
514 if (token.size() <= sizeof(nonce)) { | 566 if (token.size() <= sizeof(nonce)) { |
515 return false; | 567 return false; |
516 } | 568 } |
517 memcpy(&nonce, token.data(), sizeof(nonce)); | 569 memcpy(&nonce, token.data(), sizeof(nonce)); |
518 token.remove_prefix(sizeof(nonce)); | 570 token.remove_prefix(sizeof(nonce)); |
519 | 571 |
520 unsigned char plaintext_stack[128]; | 572 unsigned char plaintext_stack[128]; |
521 scoped_ptr<unsigned char[]> plaintext_heap; | 573 scoped_ptr<unsigned char[]> plaintext_heap; |
522 unsigned char* plaintext; | 574 unsigned char* plaintext; |
523 if (token.size() <= sizeof(plaintext_stack)) { | 575 if (token.size() <= sizeof(plaintext_stack)) { |
524 plaintext = plaintext_stack; | 576 plaintext = plaintext_stack; |
525 } else { | 577 } else { |
526 plaintext_heap.reset(new unsigned char[token.size()]); | 578 plaintext_heap.reset(new unsigned char[token.size()]); |
527 plaintext = plaintext_heap.get(); | 579 plaintext = plaintext_heap.get(); |
528 } | 580 } |
529 size_t plaintext_length; | 581 size_t plaintext_length; |
530 | 582 |
531 if (!source_address_token_decrypter_->Decrypt( | 583 if (!source_address_token_decrypter_->Decrypt( |
532 StringPiece(nonce, sizeof(nonce)), StringPiece(), token, | 584 StringPiece(nonce, sizeof(nonce)), StringPiece(), token, plaintext, |
533 plaintext, &plaintext_length)) { | 585 &plaintext_length)) { |
534 return false; | 586 return false; |
535 } | 587 } |
536 | 588 |
537 SourceAddressToken source_address_token; | 589 SourceAddressToken source_address_token; |
538 if (!source_address_token.ParseFromArray(plaintext, plaintext_length)) { | 590 if (!source_address_token.ParseFromArray(plaintext, plaintext_length)) { |
539 return false; | 591 return false; |
540 } | 592 } |
541 | 593 |
542 if (source_address_token.ip() != ip.ToString()) { | 594 if (source_address_token.ip() != ip.ToString()) { |
543 // It's for a different IP address. | 595 // It's for a different IP address. |
544 return false; | 596 return false; |
545 } | 597 } |
546 | 598 |
547 const QuicTime::Delta delta(now_since_epoch.Subtract( | 599 const QuicWallTime timestamp( |
548 QuicTime::Delta::FromSeconds(source_address_token.timestamp()))); | 600 QuicWallTime::FromUNIXSeconds(source_address_token.timestamp())); |
549 const int64 delta_secs = delta.ToSeconds(); | 601 const QuicTime::Delta delta(now.AbsoluteDifference(timestamp)); |
550 | 602 |
551 // TODO(agl): consider whether and how these magic values should be moved to | 603 // TODO(agl): consider whether and how these magic values should be moved to |
552 // a config. | 604 // a config. |
553 if (delta_secs < -3600) { | 605 if (now.IsBefore(timestamp) && delta.ToSeconds() > 3600) { |
554 // We only allow timestamps to be from an hour in the future. | 606 // We only allow timestamps to be from an hour in the future. |
555 return false; | 607 return false; |
556 } | 608 } |
557 | 609 |
558 if (delta_secs > 86400) { | 610 if (now.IsAfter(timestamp) && delta.ToSeconds() > 86400) { |
559 // We allow one day into the past. | 611 // We allow one day into the past. |
560 return false; | 612 return false; |
561 } | 613 } |
562 | 614 |
563 return true; | 615 return true; |
564 } | 616 } |
565 | 617 |
566 QuicCryptoServerConfig::Config::Config() { | 618 QuicCryptoServerConfig::Config::Config() {} |
567 } | |
568 | 619 |
569 QuicCryptoServerConfig::Config::~Config() { | 620 QuicCryptoServerConfig::Config::~Config() { STLDeleteElements(&key_exchanges); } |
570 STLDeleteElements(&key_exchanges); | |
571 } | |
572 | 621 |
573 } // namespace net | 622 } // namespace net |
OLD | NEW |