| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 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/ssl/ssl_platform_key.h" | 5 #include "net/ssl/ssl_platform_key_mac.h" |
| 6 | 6 |
| 7 #include <dlfcn.h> |
| 8 #include <CoreFoundation/CoreFoundation.h> |
| 7 #include <Security/cssm.h> | 9 #include <Security/cssm.h> |
| 8 #include <Security/SecBase.h> | 10 #include <Security/SecBase.h> |
| 9 #include <Security/SecCertificate.h> | 11 #include <Security/SecCertificate.h> |
| 10 #include <Security/SecIdentity.h> | 12 #include <Security/SecIdentity.h> |
| 11 #include <Security/SecKey.h> | 13 #include <Security/SecKey.h> |
| 12 | 14 |
| 13 #include <memory> | 15 #include <memory> |
| 14 | 16 |
| 17 #include "base/lazy_instance.h" |
| 15 #include "base/location.h" | 18 #include "base/location.h" |
| 16 #include "base/logging.h" | 19 #include "base/logging.h" |
| 20 #include "base/mac/foundation_util.h" |
| 17 #include "base/mac/mac_logging.h" | 21 #include "base/mac/mac_logging.h" |
| 22 #include "base/mac/mac_util.h" |
| 18 #include "base/mac/scoped_cftyperef.h" | 23 #include "base/mac/scoped_cftyperef.h" |
| 19 #include "base/macros.h" | 24 #include "base/macros.h" |
| 20 #include "base/memory/ptr_util.h" | 25 #include "base/memory/ptr_util.h" |
| 21 #include "base/memory/scoped_policy.h" | 26 #include "base/memory/scoped_policy.h" |
| 27 #include "base/numerics/safe_conversions.h" |
| 22 #include "base/synchronization/lock.h" | 28 #include "base/synchronization/lock.h" |
| 23 #include "crypto/mac_security_services_lock.h" | 29 #include "crypto/mac_security_services_lock.h" |
| 24 #include "crypto/openssl_util.h" | 30 #include "crypto/openssl_util.h" |
| 25 #include "net/base/net_errors.h" | 31 #include "net/base/net_errors.h" |
| 26 #include "net/cert/x509_certificate.h" | 32 #include "net/cert/x509_certificate.h" |
| 33 #include "net/ssl/ssl_platform_key.h" |
| 27 #include "net/ssl/ssl_platform_key_util.h" | 34 #include "net/ssl/ssl_platform_key_util.h" |
| 28 #include "net/ssl/ssl_private_key.h" | 35 #include "net/ssl/ssl_private_key.h" |
| 29 #include "net/ssl/threaded_ssl_private_key.h" | 36 #include "net/ssl/threaded_ssl_private_key.h" |
| 30 #include "third_party/boringssl/src/include/openssl/ecdsa.h" | 37 #include "third_party/boringssl/src/include/openssl/ecdsa.h" |
| 31 #include "third_party/boringssl/src/include/openssl/mem.h" | 38 #include "third_party/boringssl/src/include/openssl/mem.h" |
| 32 #include "third_party/boringssl/src/include/openssl/nid.h" | 39 #include "third_party/boringssl/src/include/openssl/nid.h" |
| 33 #include "third_party/boringssl/src/include/openssl/rsa.h" | 40 #include "third_party/boringssl/src/include/openssl/rsa.h" |
| 34 | 41 |
| 42 // TODO(davidben): Remove this after https://crbug.com/669240 is fixed. |
| 43 #if !defined(MAC_OS_X_VERSION_10_12) || \ |
| 44 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12 |
| 45 typedef CFStringRef SecKeyAlgorithm; |
| 46 #endif |
| 47 |
| 35 namespace net { | 48 namespace net { |
| 36 | 49 |
| 37 // CSSM functions are deprecated as of OSX 10.7, but have no replacement. | 50 // CSSM functions are deprecated as of OSX 10.7, but have no replacement. |
| 38 // https://bugs.chromium.org/p/chromium/issues/detail?id=590914#c1 | 51 // https://bugs.chromium.org/p/chromium/issues/detail?id=590914#c1 |
| 39 #pragma clang diagnostic push | 52 #pragma clang diagnostic push |
| 40 #pragma clang diagnostic ignored "-Wdeprecated-declarations" | 53 #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| 41 | 54 |
| 42 namespace { | 55 namespace { |
| 43 | 56 |
| 44 class ScopedCSSM_CC_HANDLE { | 57 class ScopedCSSM_CC_HANDLE { |
| (...skipping 10 matching lines...) Expand all Loading... |
| 55 CSSM_DeleteContext(handle_); | 68 CSSM_DeleteContext(handle_); |
| 56 handle_ = 0; | 69 handle_ = 0; |
| 57 } | 70 } |
| 58 | 71 |
| 59 private: | 72 private: |
| 60 CSSM_CC_HANDLE handle_; | 73 CSSM_CC_HANDLE handle_; |
| 61 | 74 |
| 62 DISALLOW_COPY_AND_ASSIGN(ScopedCSSM_CC_HANDLE); | 75 DISALLOW_COPY_AND_ASSIGN(ScopedCSSM_CC_HANDLE); |
| 63 }; | 76 }; |
| 64 | 77 |
| 65 // Looks up the private key for |certificate| in KeyChain and returns | 78 // Looks up the private key for |certificate| in |keychain| and returns |
| 66 // a SecKeyRef or nullptr on failure. The caller takes ownership of the | 79 // a SecKeyRef or nullptr on failure. The caller takes ownership of the |
| 67 // result. | 80 // result. |
| 68 SecKeyRef FetchSecKeyRefForCertificate(const X509Certificate* certificate) { | 81 SecKeyRef FetchSecKeyRefForCertificate(const X509Certificate* certificate, |
| 82 SecKeychainRef keychain) { |
| 69 OSStatus status; | 83 OSStatus status; |
| 70 base::ScopedCFTypeRef<SecIdentityRef> identity; | 84 base::ScopedCFTypeRef<SecIdentityRef> identity; |
| 71 { | 85 { |
| 72 base::AutoLock lock(crypto::GetMacSecurityServicesLock()); | 86 base::AutoLock lock(crypto::GetMacSecurityServicesLock()); |
| 73 status = SecIdentityCreateWithCertificate( | 87 status = SecIdentityCreateWithCertificate( |
| 74 nullptr, certificate->os_cert_handle(), identity.InitializeInto()); | 88 keychain, certificate->os_cert_handle(), identity.InitializeInto()); |
| 75 } | 89 } |
| 76 if (status != noErr) { | 90 if (status != noErr) { |
| 77 OSSTATUS_LOG(WARNING, status); | 91 OSSTATUS_LOG(WARNING, status); |
| 78 return nullptr; | 92 return nullptr; |
| 79 } | 93 } |
| 80 | 94 |
| 81 base::ScopedCFTypeRef<SecKeyRef> private_key; | 95 base::ScopedCFTypeRef<SecKeyRef> private_key; |
| 82 status = SecIdentityCopyPrivateKey(identity, private_key.InitializeInto()); | 96 status = SecIdentityCopyPrivateKey(identity, private_key.InitializeInto()); |
| 83 if (status != noErr) { | 97 if (status != noErr) { |
| 84 OSSTATUS_LOG(WARNING, status); | 98 OSSTATUS_LOG(WARNING, status); |
| 85 return nullptr; | 99 return nullptr; |
| 86 } | 100 } |
| 87 | 101 |
| 88 return private_key.release(); | 102 return private_key.release(); |
| 89 } | 103 } |
| 90 | 104 |
| 91 class SSLPlatformKeyMac : public ThreadedSSLPrivateKey::Delegate { | 105 // These symbols were added in the 10.12 SDK, but we currently use an older SDK, |
| 106 // so look them up with dlsym. |
| 107 // |
| 108 // TODO(davidben): After https://crbug.com/669240 is fixed, use the APIs |
| 109 // directly. |
| 110 struct SecKeyAPIs { |
| 111 SecKeyAPIs() { Init(); } |
| 112 |
| 113 void Init() { |
| 114 SecKeyCreateSignature = reinterpret_cast<SecKeyCreateSignatureFunc>( |
| 115 dlsym(RTLD_DEFAULT, "SecKeyCreateSignature")); |
| 116 if (!SecKeyCreateSignature) { |
| 117 NOTREACHED(); |
| 118 return; |
| 119 } |
| 120 |
| 121 #define LOOKUP_ALGORITHM(name) \ |
| 122 do { \ |
| 123 SecKeyAlgorithm* algorithm = \ |
| 124 reinterpret_cast<SecKeyAlgorithm*>(dlsym(RTLD_DEFAULT, #name)); \ |
| 125 if (!algorithm) { \ |
| 126 NOTREACHED(); \ |
| 127 return; \ |
| 128 } \ |
| 129 name = *algorithm; \ |
| 130 } while (0) |
| 131 |
| 132 LOOKUP_ALGORITHM(kSecKeyAlgorithmRSASignatureDigestPKCS1v15Raw); |
| 133 LOOKUP_ALGORITHM(kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA1); |
| 134 LOOKUP_ALGORITHM(kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256); |
| 135 LOOKUP_ALGORITHM(kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384); |
| 136 LOOKUP_ALGORITHM(kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512); |
| 137 LOOKUP_ALGORITHM(kSecKeyAlgorithmECDSASignatureDigestX962SHA1); |
| 138 LOOKUP_ALGORITHM(kSecKeyAlgorithmECDSASignatureDigestX962SHA256); |
| 139 LOOKUP_ALGORITHM(kSecKeyAlgorithmECDSASignatureDigestX962SHA384); |
| 140 LOOKUP_ALGORITHM(kSecKeyAlgorithmECDSASignatureDigestX962SHA512); |
| 141 |
| 142 #undef LOOKUP_ALGORITHM |
| 143 |
| 144 valid = true; |
| 145 } |
| 146 |
| 147 using SecKeyCreateSignatureFunc = CFDataRef (*)(SecKeyRef key, |
| 148 SecKeyAlgorithm algorithm, |
| 149 CFDataRef dataToSign, |
| 150 CFErrorRef* error); |
| 151 |
| 152 bool valid = false; |
| 153 SecKeyCreateSignatureFunc SecKeyCreateSignature = nullptr; |
| 154 SecKeyAlgorithm kSecKeyAlgorithmRSASignatureDigestPKCS1v15Raw = nullptr; |
| 155 SecKeyAlgorithm kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA1 = nullptr; |
| 156 SecKeyAlgorithm kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256 = nullptr; |
| 157 SecKeyAlgorithm kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384 = nullptr; |
| 158 SecKeyAlgorithm kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512 = nullptr; |
| 159 SecKeyAlgorithm kSecKeyAlgorithmECDSASignatureDigestX962SHA1 = nullptr; |
| 160 SecKeyAlgorithm kSecKeyAlgorithmECDSASignatureDigestX962SHA256 = nullptr; |
| 161 SecKeyAlgorithm kSecKeyAlgorithmECDSASignatureDigestX962SHA384 = nullptr; |
| 162 SecKeyAlgorithm kSecKeyAlgorithmECDSASignatureDigestX962SHA512 = nullptr; |
| 163 }; |
| 164 |
| 165 base::LazyInstance<SecKeyAPIs>::Leaky g_sec_key_apis = |
| 166 LAZY_INSTANCE_INITIALIZER; |
| 167 |
| 168 class SSLPlatformKeyCSSM : public ThreadedSSLPrivateKey::Delegate { |
| 92 public: | 169 public: |
| 93 SSLPlatformKeyMac(SSLPrivateKey::Type type, | 170 SSLPlatformKeyCSSM(SSLPrivateKey::Type type, |
| 94 size_t max_length, | 171 size_t max_length, |
| 95 SecKeyRef key, | 172 SecKeyRef key, |
| 96 const CSSM_KEY* cssm_key) | 173 const CSSM_KEY* cssm_key) |
| 97 : type_(type), | 174 : type_(type), |
| 98 max_length_(max_length), | 175 max_length_(max_length), |
| 99 key_(key, base::scoped_policy::RETAIN), | 176 key_(key, base::scoped_policy::RETAIN), |
| 100 cssm_key_(cssm_key) {} | 177 cssm_key_(cssm_key) {} |
| 101 | 178 |
| 102 ~SSLPlatformKeyMac() override {} | 179 ~SSLPlatformKeyCSSM() override {} |
| 103 | 180 |
| 104 SSLPrivateKey::Type GetType() override { return type_; } | 181 SSLPrivateKey::Type GetType() override { return type_; } |
| 105 | 182 |
| 106 std::vector<SSLPrivateKey::Hash> GetDigestPreferences() override { | 183 std::vector<SSLPrivateKey::Hash> GetDigestPreferences() override { |
| 107 static const SSLPrivateKey::Hash kHashes[] = { | 184 return std::vector<SSLPrivateKey::Hash>{ |
| 108 SSLPrivateKey::Hash::SHA512, SSLPrivateKey::Hash::SHA384, | 185 SSLPrivateKey::Hash::SHA512, SSLPrivateKey::Hash::SHA384, |
| 109 SSLPrivateKey::Hash::SHA256, SSLPrivateKey::Hash::SHA1}; | 186 SSLPrivateKey::Hash::SHA256, SSLPrivateKey::Hash::SHA1}; |
| 110 return std::vector<SSLPrivateKey::Hash>(kHashes, | |
| 111 kHashes + arraysize(kHashes)); | |
| 112 } | 187 } |
| 113 | 188 |
| 114 size_t GetMaxSignatureLengthInBytes() override { return max_length_; } | 189 size_t GetMaxSignatureLengthInBytes() override { return max_length_; } |
| 115 | 190 |
| 116 Error SignDigest(SSLPrivateKey::Hash hash, | 191 Error SignDigest(SSLPrivateKey::Hash hash, |
| 117 const base::StringPiece& input, | 192 const base::StringPiece& input, |
| 118 std::vector<uint8_t>* signature) override { | 193 std::vector<uint8_t>* signature) override { |
| 119 crypto::OpenSSLErrStackTracer tracer(FROM_HERE); | 194 crypto::OpenSSLErrStackTracer tracer(FROM_HERE); |
| 120 | 195 |
| 121 CSSM_CSP_HANDLE csp_handle; | 196 CSSM_CSP_HANDLE csp_handle; |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 199 signature->resize(signature_data.Length); | 274 signature->resize(signature_data.Length); |
| 200 return OK; | 275 return OK; |
| 201 } | 276 } |
| 202 | 277 |
| 203 private: | 278 private: |
| 204 SSLPrivateKey::Type type_; | 279 SSLPrivateKey::Type type_; |
| 205 size_t max_length_; | 280 size_t max_length_; |
| 206 base::ScopedCFTypeRef<SecKeyRef> key_; | 281 base::ScopedCFTypeRef<SecKeyRef> key_; |
| 207 const CSSM_KEY* cssm_key_; | 282 const CSSM_KEY* cssm_key_; |
| 208 | 283 |
| 209 DISALLOW_COPY_AND_ASSIGN(SSLPlatformKeyMac); | 284 DISALLOW_COPY_AND_ASSIGN(SSLPlatformKeyCSSM); |
| 285 }; |
| 286 |
| 287 class SSLPlatformKeySecKey : public ThreadedSSLPrivateKey::Delegate { |
| 288 public: |
| 289 SSLPlatformKeySecKey(SSLPrivateKey::Type type, |
| 290 size_t max_length, |
| 291 SecKeyRef key) |
| 292 : type_(type), |
| 293 max_length_(max_length), |
| 294 key_(key, base::scoped_policy::RETAIN) {} |
| 295 |
| 296 ~SSLPlatformKeySecKey() override {} |
| 297 |
| 298 SSLPrivateKey::Type GetType() override { return type_; } |
| 299 |
| 300 std::vector<SSLPrivateKey::Hash> GetDigestPreferences() override { |
| 301 return std::vector<SSLPrivateKey::Hash>{ |
| 302 SSLPrivateKey::Hash::SHA512, SSLPrivateKey::Hash::SHA384, |
| 303 SSLPrivateKey::Hash::SHA256, SSLPrivateKey::Hash::SHA1}; |
| 304 } |
| 305 |
| 306 size_t GetMaxSignatureLengthInBytes() override { return max_length_; } |
| 307 |
| 308 Error SignDigest(SSLPrivateKey::Hash hash, |
| 309 const base::StringPiece& input, |
| 310 std::vector<uint8_t>* signature) override { |
| 311 const SecKeyAPIs& apis = g_sec_key_apis.Get(); |
| 312 if (!apis.valid) { |
| 313 LOG(ERROR) << "SecKey APIs not found"; |
| 314 return ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED; |
| 315 } |
| 316 |
| 317 SecKeyAlgorithm algorithm = nullptr; |
| 318 if (type_ == SSLPrivateKey::Type::RSA) { |
| 319 switch (hash) { |
| 320 case SSLPrivateKey::Hash::SHA512: |
| 321 algorithm = apis.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA512; |
| 322 break; |
| 323 case SSLPrivateKey::Hash::SHA384: |
| 324 algorithm = apis.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA384; |
| 325 break; |
| 326 case SSLPrivateKey::Hash::SHA256: |
| 327 algorithm = apis.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA256; |
| 328 break; |
| 329 case SSLPrivateKey::Hash::SHA1: |
| 330 algorithm = apis.kSecKeyAlgorithmRSASignatureDigestPKCS1v15SHA1; |
| 331 break; |
| 332 case SSLPrivateKey::Hash::MD5_SHA1: |
| 333 algorithm = apis.kSecKeyAlgorithmRSASignatureDigestPKCS1v15Raw; |
| 334 break; |
| 335 } |
| 336 } else if (SSLPrivateKey::IsECDSAType(type_)) { |
| 337 switch (hash) { |
| 338 case SSLPrivateKey::Hash::SHA512: |
| 339 algorithm = apis.kSecKeyAlgorithmECDSASignatureDigestX962SHA512; |
| 340 break; |
| 341 case SSLPrivateKey::Hash::SHA384: |
| 342 algorithm = apis.kSecKeyAlgorithmECDSASignatureDigestX962SHA384; |
| 343 break; |
| 344 case SSLPrivateKey::Hash::SHA256: |
| 345 algorithm = apis.kSecKeyAlgorithmECDSASignatureDigestX962SHA256; |
| 346 break; |
| 347 case SSLPrivateKey::Hash::SHA1: |
| 348 algorithm = apis.kSecKeyAlgorithmECDSASignatureDigestX962SHA1; |
| 349 break; |
| 350 case SSLPrivateKey::Hash::MD5_SHA1: |
| 351 // MD5-SHA1 is not used with ECDSA. |
| 352 break; |
| 353 } |
| 354 } |
| 355 |
| 356 if (!algorithm) { |
| 357 NOTREACHED(); |
| 358 return ERR_FAILED; |
| 359 } |
| 360 |
| 361 base::ScopedCFTypeRef<CFDataRef> input_ref(CFDataCreateWithBytesNoCopy( |
| 362 kCFAllocatorDefault, reinterpret_cast<const uint8_t*>(input.data()), |
| 363 base::checked_cast<CFIndex>(input.size()), kCFAllocatorNull)); |
| 364 |
| 365 base::ScopedCFTypeRef<CFErrorRef> error; |
| 366 base::ScopedCFTypeRef<CFDataRef> signature_ref(apis.SecKeyCreateSignature( |
| 367 key_, algorithm, input_ref, error.InitializeInto())); |
| 368 if (!signature_ref) { |
| 369 LOG(ERROR) << error; |
| 370 return ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED; |
| 371 } |
| 372 |
| 373 signature->assign( |
| 374 CFDataGetBytePtr(signature_ref), |
| 375 CFDataGetBytePtr(signature_ref) + CFDataGetLength(signature_ref)); |
| 376 return OK; |
| 377 } |
| 378 |
| 379 private: |
| 380 SSLPrivateKey::Type type_; |
| 381 size_t max_length_; |
| 382 base::ScopedCFTypeRef<SecKeyRef> key_; |
| 383 |
| 384 DISALLOW_COPY_AND_ASSIGN(SSLPlatformKeySecKey); |
| 210 }; | 385 }; |
| 211 | 386 |
| 212 } // namespace | 387 } // namespace |
| 213 | 388 |
| 214 scoped_refptr<SSLPrivateKey> FetchClientCertPrivateKey( | 389 scoped_refptr<SSLPrivateKey> FetchClientCertPrivateKeyFromKeychain( |
| 215 X509Certificate* certificate) { | 390 const X509Certificate* certificate, |
| 391 SecKeychainRef keychain) { |
| 216 // Look up the private key. | 392 // Look up the private key. |
| 217 base::ScopedCFTypeRef<SecKeyRef> private_key( | 393 base::ScopedCFTypeRef<SecKeyRef> private_key( |
| 218 FetchSecKeyRefForCertificate(certificate)); | 394 FetchSecKeyRefForCertificate(certificate, keychain)); |
| 219 if (!private_key) | 395 if (!private_key) |
| 220 return nullptr; | 396 return nullptr; |
| 221 | 397 |
| 398 SSLPrivateKey::Type key_type; |
| 399 size_t max_length; |
| 400 if (!GetClientCertInfo(certificate, &key_type, &max_length)) |
| 401 return nullptr; |
| 402 |
| 403 if (base::mac::IsAtLeastOS10_12()) { |
| 404 return make_scoped_refptr( |
| 405 new ThreadedSSLPrivateKey(base::MakeUnique<SSLPlatformKeySecKey>( |
| 406 key_type, max_length, private_key.get()), |
| 407 GetSSLPlatformKeyTaskRunner())); |
| 408 } |
| 409 |
| 222 const CSSM_KEY* cssm_key; | 410 const CSSM_KEY* cssm_key; |
| 223 OSStatus status = SecKeyGetCSSMKey(private_key.get(), &cssm_key); | 411 OSStatus status = SecKeyGetCSSMKey(private_key.get(), &cssm_key); |
| 224 if (status != noErr) { | 412 if (status != noErr) { |
| 225 OSSTATUS_LOG(WARNING, status); | 413 OSSTATUS_LOG(WARNING, status); |
| 226 return nullptr; | 414 return nullptr; |
| 227 } | 415 } |
| 228 | 416 |
| 229 SSLPrivateKey::Type key_type; | 417 return make_scoped_refptr(new ThreadedSSLPrivateKey( |
| 230 size_t max_length; | 418 base::MakeUnique<SSLPlatformKeyCSSM>(key_type, max_length, |
| 231 if (!GetClientCertInfo(certificate, &key_type, &max_length)) | 419 private_key.get(), cssm_key), |
| 232 return nullptr; | 420 GetSSLPlatformKeyTaskRunner())); |
| 421 } |
| 233 | 422 |
| 234 return make_scoped_refptr(new ThreadedSSLPrivateKey( | 423 scoped_refptr<SSLPrivateKey> FetchClientCertPrivateKey( |
| 235 base::MakeUnique<SSLPlatformKeyMac>(key_type, max_length, | 424 const X509Certificate* certificate) { |
| 236 private_key.get(), cssm_key), | 425 return FetchClientCertPrivateKeyFromKeychain(certificate, nullptr); |
| 237 GetSSLPlatformKeyTaskRunner())); | |
| 238 } | 426 } |
| 239 | 427 |
| 240 #pragma clang diagnostic pop // "-Wdeprecated-declarations" | 428 #pragma clang diagnostic pop // "-Wdeprecated-declarations" |
| 241 | 429 |
| 242 } // namespace net | 430 } // namespace net |
| OLD | NEW |