1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/test/revocation_builder.h"
6
7 #include "base/functional/callback.h"
8 #include "base/hash/sha1.h"
9 #include "base/strings/string_piece.h"
10 #include "base/strings/string_util.h"
11 #include "base/test/bind.h"
12 #include "net/cert/asn1_util.h"
13 #include "net/cert/x509_util.h"
14 #include "net/der/encode_values.h"
15 #include "net/der/input.h"
16 #include "net/test/cert_builder.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include "third_party/boringssl/src/include/openssl/bytestring.h"
19 #include "third_party/boringssl/src/include/openssl/mem.h"
20
21 namespace net {
22
23 namespace {
24
Sha1()25 std::string Sha1() {
26 // SEQUENCE { OBJECT_IDENTIFIER { 1.3.14.3.2.26 } }
27 const uint8_t kSHA1[] = {0x30, 0x07, 0x06, 0x05, 0x2b,
28 0x0e, 0x03, 0x02, 0x1a};
29 return std::string(std::begin(kSHA1), std::end(kSHA1));
30 }
31
32 // Adds bytes (specified as a StringPiece) to the given CBB.
33 // The argument ordering follows the boringssl CBB_* api style.
CBBAddBytes(CBB * cbb,base::StringPiece bytes)34 bool CBBAddBytes(CBB* cbb, base::StringPiece bytes) {
35 return CBB_add_bytes(cbb, reinterpret_cast<const uint8_t*>(bytes.data()),
36 bytes.size());
37 }
38
39 // Adds bytes (from fixed size array) to the given CBB.
40 // The argument ordering follows the boringssl CBB_* api style.
41 template <size_t N>
CBBAddBytes(CBB * cbb,const uint8_t (& data)[N])42 bool CBBAddBytes(CBB* cbb, const uint8_t (&data)[N]) {
43 return CBB_add_bytes(cbb, data, N);
44 }
45
46 // Adds a GeneralizedTime value to the given CBB.
47 // The argument ordering follows the boringssl CBB_* api style.
CBBAddGeneralizedTime(CBB * cbb,const base::Time & time)48 bool CBBAddGeneralizedTime(CBB* cbb, const base::Time& time) {
49 der::GeneralizedTime generalized_time;
50 if (!der::EncodeTimeAsGeneralizedTime(time, &generalized_time))
51 return false;
52 CBB time_cbb;
53 uint8_t out[der::kGeneralizedTimeLength];
54 if (!der::EncodeGeneralizedTime(generalized_time, out) ||
55 !CBB_add_asn1(cbb, &time_cbb, CBS_ASN1_GENERALIZEDTIME) ||
56 !CBBAddBytes(&time_cbb, out) || !CBB_flush(cbb))
57 return false;
58 return true;
59 }
60
61 // Finalizes the CBB to a std::string.
FinishCBB(CBB * cbb)62 std::string FinishCBB(CBB* cbb) {
63 size_t cbb_len;
64 uint8_t* cbb_bytes;
65
66 if (!CBB_finish(cbb, &cbb_bytes, &cbb_len)) {
67 ADD_FAILURE() << "CBB_finish() failed";
68 return std::string();
69 }
70
71 bssl::UniquePtr<uint8_t> delete_bytes(cbb_bytes);
72 return std::string(reinterpret_cast<char*>(cbb_bytes), cbb_len);
73 }
74
PKeyToSPK(const EVP_PKEY * pkey)75 std::string PKeyToSPK(const EVP_PKEY* pkey) {
76 bssl::ScopedCBB cbb;
77 if (!CBB_init(cbb.get(), 64) || !EVP_marshal_public_key(cbb.get(), pkey)) {
78 ADD_FAILURE();
79 return std::string();
80 }
81 std::string spki = FinishCBB(cbb.get());
82
83 base::StringPiece spk;
84 if (!asn1::ExtractSubjectPublicKeyFromSPKI(spki, &spk)) {
85 ADD_FAILURE();
86 return std::string();
87 }
88
89 // ExtractSubjectPublicKeyFromSPKI() includes the unused bit count. For this
90 // application, the unused bit count must be zero, and is not included in the
91 // result.
92 if (!base::StartsWith(spk, "\0")) {
93 ADD_FAILURE();
94 return std::string();
95 }
96 spk.remove_prefix(1);
97
98 return std::string(spk);
99 }
100
101 // Returns a DER-encoded OCSPResponse with the given |response_status|.
102 // |response_type| and |response| are optional and may be empty.
EncodeOCSPResponse(OCSPResponse::ResponseStatus response_status,der::Input response_type,std::string response)103 std::string EncodeOCSPResponse(OCSPResponse::ResponseStatus response_status,
104 der::Input response_type,
105 std::string response) {
106 // RFC 6960 section 4.2.1:
107 //
108 // OCSPResponse ::= SEQUENCE {
109 // responseStatus OCSPResponseStatus,
110 // responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
111 //
112 // OCSPResponseStatus ::= ENUMERATED {
113 // successful (0), -- Response has valid confirmations
114 // malformedRequest (1), -- Illegal confirmation request
115 // internalError (2), -- Internal error in issuer
116 // tryLater (3), -- Try again later
117 // -- (4) is not used
118 // sigRequired (5), -- Must sign the request
119 // unauthorized (6) -- Request unauthorized
120 // }
121 //
122 // The value for responseBytes consists of an OBJECT IDENTIFIER and a
123 // response syntax identified by that OID encoded as an OCTET STRING.
124 //
125 // ResponseBytes ::= SEQUENCE {
126 // responseType OBJECT IDENTIFIER,
127 // response OCTET STRING }
128 bssl::ScopedCBB cbb;
129 CBB ocsp_response, ocsp_response_status, ocsp_response_bytes,
130 ocsp_response_bytes_sequence, ocsp_response_type,
131 ocsp_response_octet_string;
132
133 if (!CBB_init(cbb.get(), 64 + response_type.Length() + response.size()) ||
134 !CBB_add_asn1(cbb.get(), &ocsp_response, CBS_ASN1_SEQUENCE) ||
135 !CBB_add_asn1(&ocsp_response, &ocsp_response_status,
136 CBS_ASN1_ENUMERATED) ||
137 !CBB_add_u8(&ocsp_response_status,
138 static_cast<uint8_t>(response_status))) {
139 ADD_FAILURE();
140 return std::string();
141 }
142
143 if (response_type.Length()) {
144 if (!CBB_add_asn1(&ocsp_response, &ocsp_response_bytes,
145 CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
146 !CBB_add_asn1(&ocsp_response_bytes, &ocsp_response_bytes_sequence,
147 CBS_ASN1_SEQUENCE) ||
148 !CBB_add_asn1(&ocsp_response_bytes_sequence, &ocsp_response_type,
149 CBS_ASN1_OBJECT) ||
150 !CBBAddBytes(&ocsp_response_type, response_type.AsStringView()) ||
151 !CBB_add_asn1(&ocsp_response_bytes_sequence,
152 &ocsp_response_octet_string, CBS_ASN1_OCTETSTRING) ||
153 !CBBAddBytes(&ocsp_response_octet_string, response)) {
154 ADD_FAILURE();
155 return std::string();
156 }
157 }
158
159 return FinishCBB(cbb.get());
160 }
161
162 // Adds a DER-encoded OCSP SingleResponse to |responses_cbb|.
163 // |issuer_name_hash| and |issuer_key_hash| should be binary SHA1 hashes.
AddOCSPSingleResponse(CBB * responses_cbb,const OCSPBuilderSingleResponse & response,const std::string & issuer_name_hash,const std::string & issuer_key_hash)164 bool AddOCSPSingleResponse(CBB* responses_cbb,
165 const OCSPBuilderSingleResponse& response,
166 const std::string& issuer_name_hash,
167 const std::string& issuer_key_hash) {
168 // RFC 6960 section 4.2.1:
169 //
170 // SingleResponse ::= SEQUENCE {
171 // certID CertID,
172 // certStatus CertStatus,
173 // thisUpdate GeneralizedTime,
174 // nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
175 // singleExtensions [1] EXPLICIT Extensions OPTIONAL }
176 //
177 // CertStatus ::= CHOICE {
178 // good [0] IMPLICIT NULL,
179 // revoked [1] IMPLICIT RevokedInfo,
180 // unknown [2] IMPLICIT UnknownInfo }
181 //
182 // RevokedInfo ::= SEQUENCE {
183 // revocationTime GeneralizedTime,
184 // revocationReason [0] EXPLICIT CRLReason OPTIONAL }
185 //
186 // UnknownInfo ::= NULL
187 //
188 // RFC 6960 section 4.1.1:
189 // CertID ::= SEQUENCE {
190 // hashAlgorithm AlgorithmIdentifier,
191 // issuerNameHash OCTET STRING, -- Hash of issuer's DN
192 // issuerKeyHash OCTET STRING, -- Hash of issuer's public key
193 // serialNumber CertificateSerialNumber }
194 //
195 // The contents of CertID include the following fields:
196 //
197 // o hashAlgorithm is the hash algorithm used to generate the
198 // issuerNameHash and issuerKeyHash values.
199 //
200 // o issuerNameHash is the hash of the issuer's distinguished name
201 // (DN). The hash shall be calculated over the DER encoding of the
202 // issuer's name field in the certificate being checked.
203 //
204 // o issuerKeyHash is the hash of the issuer's public key. The hash
205 // shall be calculated over the value (excluding tag and length) of
206 // the subject public key field in the issuer's certificate.
207 //
208 // o serialNumber is the serial number of the certificate for which
209 // status is being requested.
210
211 CBB single_response, issuer_name_hash_cbb, issuer_key_hash_cbb, cert_id;
212 if (!CBB_add_asn1(responses_cbb, &single_response, CBS_ASN1_SEQUENCE) ||
213 !CBB_add_asn1(&single_response, &cert_id, CBS_ASN1_SEQUENCE) ||
214 !CBBAddBytes(&cert_id, Sha1()) ||
215 !CBB_add_asn1(&cert_id, &issuer_name_hash_cbb, CBS_ASN1_OCTETSTRING) ||
216 !CBBAddBytes(&issuer_name_hash_cbb, issuer_name_hash) ||
217 !CBB_add_asn1(&cert_id, &issuer_key_hash_cbb, CBS_ASN1_OCTETSTRING) ||
218 !CBBAddBytes(&issuer_key_hash_cbb, issuer_key_hash) ||
219 !CBB_add_asn1_uint64(&cert_id, response.serial)) {
220 ADD_FAILURE();
221 return false;
222 }
223
224 unsigned int cert_status_tag_number;
225 switch (response.cert_status) {
226 case OCSPRevocationStatus::GOOD:
227 cert_status_tag_number = CBS_ASN1_CONTEXT_SPECIFIC | 0;
228 break;
229 case OCSPRevocationStatus::REVOKED:
230 cert_status_tag_number =
231 CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1;
232 break;
233 case OCSPRevocationStatus::UNKNOWN:
234 cert_status_tag_number = CBS_ASN1_CONTEXT_SPECIFIC | 2;
235 break;
236 }
237
238 CBB cert_status_cbb;
239 if (!CBB_add_asn1(&single_response, &cert_status_cbb,
240 cert_status_tag_number)) {
241 ADD_FAILURE();
242 return false;
243 }
244 if (response.cert_status == OCSPRevocationStatus::REVOKED &&
245 !CBBAddGeneralizedTime(&cert_status_cbb, response.revocation_time)) {
246 ADD_FAILURE();
247 return false;
248 }
249
250 CBB next_update_cbb;
251 if (!CBBAddGeneralizedTime(&single_response, response.this_update) ||
252 !CBB_add_asn1(&single_response, &next_update_cbb,
253 CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
254 !CBBAddGeneralizedTime(&next_update_cbb, response.next_update)) {
255 ADD_FAILURE();
256 return false;
257 }
258
259 return CBB_flush(responses_cbb);
260 }
261
262 } // namespace
263
BuildOCSPResponseError(OCSPResponse::ResponseStatus response_status)264 std::string BuildOCSPResponseError(
265 OCSPResponse::ResponseStatus response_status) {
266 DCHECK_NE(response_status, OCSPResponse::ResponseStatus::SUCCESSFUL);
267 return EncodeOCSPResponse(response_status, der::Input(), std::string());
268 }
269
BuildOCSPResponse(const std::string & responder_subject,EVP_PKEY * responder_key,base::Time produced_at,const std::vector<OCSPBuilderSingleResponse> & responses)270 std::string BuildOCSPResponse(
271 const std::string& responder_subject,
272 EVP_PKEY* responder_key,
273 base::Time produced_at,
274 const std::vector<OCSPBuilderSingleResponse>& responses) {
275 std::string responder_name_hash = base::SHA1HashString(responder_subject);
276 std::string responder_key_hash =
277 base::SHA1HashString(PKeyToSPK(responder_key));
278
279 // RFC 6960 section 4.2.1:
280 //
281 // ResponseData ::= SEQUENCE {
282 // version [0] EXPLICIT Version DEFAULT v1,
283 // responderID ResponderID,
284 // producedAt GeneralizedTime,
285 // responses SEQUENCE OF SingleResponse,
286 // responseExtensions [1] EXPLICIT Extensions OPTIONAL }
287 //
288 // ResponderID ::= CHOICE {
289 // byName [1] Name,
290 // byKey [2] KeyHash }
291 //
292 // KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
293 // (excluding the tag and length fields)
294 bssl::ScopedCBB tbs_cbb;
295 CBB response_data, responder_id, responder_id_by_key, responses_cbb;
296 if (!CBB_init(tbs_cbb.get(), 64) ||
297 !CBB_add_asn1(tbs_cbb.get(), &response_data, CBS_ASN1_SEQUENCE) ||
298 // Version is the default v1, so it is not encoded.
299 !CBB_add_asn1(&response_data, &responder_id,
300 CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 2) ||
301 !CBB_add_asn1(&responder_id, &responder_id_by_key,
302 CBS_ASN1_OCTETSTRING) ||
303 !CBBAddBytes(&responder_id_by_key, responder_key_hash) ||
304 !CBBAddGeneralizedTime(&response_data, produced_at) ||
305 !CBB_add_asn1(&response_data, &responses_cbb, CBS_ASN1_SEQUENCE)) {
306 ADD_FAILURE();
307 return std::string();
308 }
309
310 for (const auto& response : responses) {
311 if (!AddOCSPSingleResponse(&responses_cbb, response, responder_name_hash,
312 responder_key_hash)) {
313 return std::string();
314 }
315 }
316
317 // responseExtensions not currently supported.
318
319 return BuildOCSPResponseWithResponseData(responder_key,
320 FinishCBB(tbs_cbb.get()));
321 }
322
BuildOCSPResponseWithResponseData(EVP_PKEY * responder_key,const std::string & tbs_response_data,absl::optional<SignatureAlgorithm> signature_algorithm)323 std::string BuildOCSPResponseWithResponseData(
324 EVP_PKEY* responder_key,
325 const std::string& tbs_response_data,
326 absl::optional<SignatureAlgorithm> signature_algorithm) {
327 // For a basic OCSP responder, responseType will be id-pkix-ocsp-basic.
328 //
329 // id-pkix-ocsp OBJECT IDENTIFIER ::= { id-ad-ocsp }
330 // id-pkix-ocsp-basic OBJECT IDENTIFIER ::= { id-pkix-ocsp 1 }
331 //
332 // The value for response SHALL be the DER encoding of
333 // BasicOCSPResponse.
334 //
335 // BasicOCSPResponse ::= SEQUENCE {
336 // tbsResponseData ResponseData,
337 // signatureAlgorithm AlgorithmIdentifier,
338 // signature BIT STRING,
339 // certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
340 //
341 // The value for signature SHALL be computed on the hash of the DER
342 // encoding of ResponseData. The responder MAY include certificates in
343 // the certs field of BasicOCSPResponse that help the OCSP client verify
344 // the responder's signature. If no certificates are included, then
345 // certs SHOULD be absent.
346 //
347 bssl::ScopedCBB basic_ocsp_response_cbb;
348 CBB basic_ocsp_response, signature;
349 if (!responder_key) {
350 ADD_FAILURE();
351 return std::string();
352 }
353 if (!signature_algorithm)
354 signature_algorithm =
355 CertBuilder::DefaultSignatureAlgorithmForKey(responder_key);
356 if (!signature_algorithm) {
357 ADD_FAILURE();
358 return std::string();
359 }
360 std::string signature_algorithm_tlv =
361 CertBuilder::SignatureAlgorithmToDer(*signature_algorithm);
362 if (signature_algorithm_tlv.empty() ||
363 !CBB_init(basic_ocsp_response_cbb.get(), 64 + tbs_response_data.size()) ||
364 !CBB_add_asn1(basic_ocsp_response_cbb.get(), &basic_ocsp_response,
365 CBS_ASN1_SEQUENCE) ||
366 !CBBAddBytes(&basic_ocsp_response, tbs_response_data) ||
367 !CBBAddBytes(&basic_ocsp_response, signature_algorithm_tlv) ||
368 !CBB_add_asn1(&basic_ocsp_response, &signature, CBS_ASN1_BITSTRING) ||
369 !CBB_add_u8(&signature, 0 /* no unused bits */) ||
370 !CertBuilder::SignData(*signature_algorithm, tbs_response_data,
371 responder_key, &signature)) {
372 ADD_FAILURE();
373 return std::string();
374 }
375
376 // certs field not currently supported.
377
378 return EncodeOCSPResponse(OCSPResponse::ResponseStatus::SUCCESSFUL,
379 der::Input(kBasicOCSPResponseOid),
380 FinishCBB(basic_ocsp_response_cbb.get()));
381 }
382
BuildCrlWithSigner(const std::string & crl_issuer_subject,EVP_PKEY * crl_issuer_key,const std::vector<uint64_t> & revoked_serials,const std::string & signature_algorithm_tlv,base::OnceCallback<bool (std::string,CBB *)> signer)383 std::string BuildCrlWithSigner(
384 const std::string& crl_issuer_subject,
385 EVP_PKEY* crl_issuer_key,
386 const std::vector<uint64_t>& revoked_serials,
387 const std::string& signature_algorithm_tlv,
388 base::OnceCallback<bool(std::string, CBB*)> signer) {
389 if (!crl_issuer_key) {
390 ADD_FAILURE();
391 return std::string();
392 }
393 // TBSCertList ::= SEQUENCE {
394 // version Version OPTIONAL,
395 // -- if present, MUST be v2
396 // signature AlgorithmIdentifier,
397 // issuer Name,
398 // thisUpdate Time,
399 // nextUpdate Time OPTIONAL,
400 // revokedCertificates SEQUENCE OF SEQUENCE {
401 // userCertificate CertificateSerialNumber,
402 // revocationDate Time,
403 // crlEntryExtensions Extensions OPTIONAL
404 // -- if present, version MUST be v2
405 // } OPTIONAL,
406 // crlExtensions [0] EXPLICIT Extensions OPTIONAL
407 // -- if present, version MUST be v2
408 // }
409 bssl::ScopedCBB tbs_cbb;
410 CBB tbs_cert_list, revoked_serials_cbb;
411 if (!CBB_init(tbs_cbb.get(), 10) ||
412 !CBB_add_asn1(tbs_cbb.get(), &tbs_cert_list, CBS_ASN1_SEQUENCE) ||
413 !CBB_add_asn1_uint64(&tbs_cert_list, 1 /* V2 */) ||
414 !CBBAddBytes(&tbs_cert_list, signature_algorithm_tlv) ||
415 !CBBAddBytes(&tbs_cert_list, crl_issuer_subject) ||
416 !x509_util::CBBAddTime(&tbs_cert_list,
417 base::Time::Now() - base::Days(1)) ||
418 !x509_util::CBBAddTime(&tbs_cert_list,
419 base::Time::Now() + base::Days(6))) {
420 ADD_FAILURE();
421 return std::string();
422 }
423 if (!revoked_serials.empty()) {
424 if (!CBB_add_asn1(&tbs_cert_list, &revoked_serials_cbb,
425 CBS_ASN1_SEQUENCE)) {
426 ADD_FAILURE();
427 return std::string();
428 }
429 for (const int64_t revoked_serial : revoked_serials) {
430 CBB revoked_serial_cbb;
431 if (!CBB_add_asn1(&revoked_serials_cbb, &revoked_serial_cbb,
432 CBS_ASN1_SEQUENCE) ||
433 !CBB_add_asn1_uint64(&revoked_serial_cbb, revoked_serial) ||
434 !x509_util::CBBAddTime(&revoked_serial_cbb,
435 base::Time::Now() - base::Days(1)) ||
436 !CBB_flush(&revoked_serials_cbb)) {
437 ADD_FAILURE();
438 return std::string();
439 }
440 }
441 }
442
443 std::string tbs_tlv = FinishCBB(tbs_cbb.get());
444
445 // CertificateList ::= SEQUENCE {
446 // tbsCertList TBSCertList,
447 // signatureAlgorithm AlgorithmIdentifier,
448 // signatureValue BIT STRING }
449 bssl::ScopedCBB crl_cbb;
450 CBB cert_list, signature;
451 if (!CBB_init(crl_cbb.get(), 10) ||
452 !CBB_add_asn1(crl_cbb.get(), &cert_list, CBS_ASN1_SEQUENCE) ||
453 !CBBAddBytes(&cert_list, tbs_tlv) ||
454 !CBBAddBytes(&cert_list, signature_algorithm_tlv) ||
455 !CBB_add_asn1(&cert_list, &signature, CBS_ASN1_BITSTRING) ||
456 !CBB_add_u8(&signature, 0 /* no unused bits */) ||
457 !std::move(signer).Run(tbs_tlv, &signature)) {
458 ADD_FAILURE();
459 return std::string();
460 }
461 return FinishCBB(crl_cbb.get());
462 }
463
BuildCrl(const std::string & crl_issuer_subject,EVP_PKEY * crl_issuer_key,const std::vector<uint64_t> & revoked_serials,absl::optional<SignatureAlgorithm> signature_algorithm)464 std::string BuildCrl(const std::string& crl_issuer_subject,
465 EVP_PKEY* crl_issuer_key,
466 const std::vector<uint64_t>& revoked_serials,
467 absl::optional<SignatureAlgorithm> signature_algorithm) {
468 if (!signature_algorithm) {
469 signature_algorithm =
470 CertBuilder::DefaultSignatureAlgorithmForKey(crl_issuer_key);
471 }
472 if (!signature_algorithm) {
473 ADD_FAILURE();
474 return std::string();
475 }
476 std::string signature_algorithm_tlv =
477 CertBuilder::SignatureAlgorithmToDer(*signature_algorithm);
478 if (signature_algorithm_tlv.empty()) {
479 ADD_FAILURE();
480 return std::string();
481 }
482
483 auto signer =
484 base::BindLambdaForTesting([&](std::string tbs_tlv, CBB* signature) {
485 return CertBuilder::SignData(*signature_algorithm, tbs_tlv,
486 crl_issuer_key, signature);
487 });
488 return BuildCrlWithSigner(crl_issuer_subject, crl_issuer_key, revoked_serials,
489 signature_algorithm_tlv, signer);
490 }
491
BuildCrlWithAlgorithmTlvAndDigest(const std::string & crl_issuer_subject,EVP_PKEY * crl_issuer_key,const std::vector<uint64_t> & revoked_serials,const std::string & signature_algorithm_tlv,const EVP_MD * digest)492 std::string BuildCrlWithAlgorithmTlvAndDigest(
493 const std::string& crl_issuer_subject,
494 EVP_PKEY* crl_issuer_key,
495 const std::vector<uint64_t>& revoked_serials,
496 const std::string& signature_algorithm_tlv,
497 const EVP_MD* digest) {
498 auto signer =
499 base::BindLambdaForTesting([&](std::string tbs_tlv, CBB* signature) {
500 return CertBuilder::SignDataWithDigest(digest, tbs_tlv, crl_issuer_key,
501 signature);
502 });
503 return BuildCrlWithSigner(crl_issuer_subject, crl_issuer_key, revoked_serials,
504 signature_algorithm_tlv, signer);
505 }
506
507 } // namespace net
508