Chromium Code Reviews| 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 "content/renderer/webcrypto/webcrypto_impl.h" | 5 #include "content/renderer/webcrypto/webcrypto_impl.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <functional> | 8 #include <functional> |
| 9 #include <map> | 9 #include <map> |
| 10 #include "base/json/json_reader.h" | 10 #include "base/json/json_reader.h" |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 31 } | 31 } |
| 32 | 32 |
| 33 bool IsAlgorithmAsymmetric(const blink::WebCryptoAlgorithm& algorithm) { | 33 bool IsAlgorithmAsymmetric(const blink::WebCryptoAlgorithm& algorithm) { |
| 34 // TODO(padolph): include all other asymmetric algorithms once they are | 34 // TODO(padolph): include all other asymmetric algorithms once they are |
| 35 // defined, e.g. EC and DH. | 35 // defined, e.g. EC and DH. |
| 36 return (algorithm.id() == blink::WebCryptoAlgorithmIdRsaEsPkcs1v1_5 || | 36 return (algorithm.id() == blink::WebCryptoAlgorithmIdRsaEsPkcs1v1_5 || |
| 37 algorithm.id() == blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5 || | 37 algorithm.id() == blink::WebCryptoAlgorithmIdRsaSsaPkcs1v1_5 || |
| 38 algorithm.id() == blink::WebCryptoAlgorithmIdRsaOaep); | 38 algorithm.id() == blink::WebCryptoAlgorithmIdRsaOaep); |
| 39 } | 39 } |
| 40 | 40 |
| 41 // Binds a specific key length value to a compatible factory function. | 41 typedef blink::WebCryptoAlgorithm (*AlgorithmCreationFunc)(); |
| 42 typedef blink::WebCryptoAlgorithm (*AlgFactoryFuncWithOneShortArg)( | |
| 43 unsigned short); | |
| 44 template <AlgFactoryFuncWithOneShortArg func, unsigned short key_length> | |
| 45 blink::WebCryptoAlgorithm BindAlgFactoryWithKeyLen() { | |
| 46 return func(key_length); | |
| 47 } | |
| 48 | 42 |
| 49 // Binds a WebCryptoAlgorithmId value to a compatible factory function. | 43 class JwkAlgorithmInfo { |
| 50 typedef blink::WebCryptoAlgorithm (*AlgFactoryFuncWithWebCryptoAlgIdArg)( | 44 public: |
| 51 blink::WebCryptoAlgorithmId); | 45 JwkAlgorithmInfo() : |
|
Ryan Sleevi
2014/02/04 02:36:35
style:
: creation_func_(NULL),
required_key_len
eroman
2014/02/04 02:46:03
Done.
| |
| 52 template <AlgFactoryFuncWithWebCryptoAlgIdArg func, | 46 creation_func_(NULL), |
| 53 blink::WebCryptoAlgorithmId algorithm_id> | 47 required_key_length_bytes_(NO_KEY_SIZE_REQUIREMENT) { |
| 54 blink::WebCryptoAlgorithm BindAlgFactoryAlgorithmId() { | |
| 55 return func(algorithm_id); | |
| 56 } | |
| 57 | 48 |
| 58 // Defines a map between a JWK 'alg' string ID and a corresponding Web Crypto | |
| 59 // factory function. | |
| 60 typedef blink::WebCryptoAlgorithm (*AlgFactoryFuncNoArgs)(); | |
| 61 typedef std::map<std::string, AlgFactoryFuncNoArgs> JwkAlgFactoryMap; | |
| 62 | |
| 63 class JwkAlgorithmFactoryMap { | |
| 64 public: | |
| 65 JwkAlgorithmFactoryMap() { | |
| 66 map_["HS256"] = | |
| 67 &BindAlgFactoryAlgorithmId<webcrypto::CreateHmacAlgorithmByHashId, | |
| 68 blink::WebCryptoAlgorithmIdSha256>; | |
| 69 map_["HS384"] = | |
| 70 &BindAlgFactoryAlgorithmId<webcrypto::CreateHmacAlgorithmByHashId, | |
| 71 blink::WebCryptoAlgorithmIdSha384>; | |
| 72 map_["HS512"] = | |
| 73 &BindAlgFactoryAlgorithmId<webcrypto::CreateHmacAlgorithmByHashId, | |
| 74 blink::WebCryptoAlgorithmIdSha512>; | |
| 75 map_["RS256"] = | |
| 76 &BindAlgFactoryAlgorithmId<webcrypto::CreateRsaSsaAlgorithm, | |
| 77 blink::WebCryptoAlgorithmIdSha256>; | |
| 78 map_["RS384"] = | |
| 79 &BindAlgFactoryAlgorithmId<webcrypto::CreateRsaSsaAlgorithm, | |
| 80 blink::WebCryptoAlgorithmIdSha384>; | |
| 81 map_["RS512"] = | |
| 82 &BindAlgFactoryAlgorithmId<webcrypto::CreateRsaSsaAlgorithm, | |
| 83 blink::WebCryptoAlgorithmIdSha512>; | |
| 84 map_["RSA1_5"] = | |
| 85 &BindAlgFactoryAlgorithmId<webcrypto::CreateAlgorithm, | |
| 86 blink::WebCryptoAlgorithmIdRsaEsPkcs1v1_5>; | |
| 87 map_["RSA-OAEP"] = | |
| 88 &BindAlgFactoryAlgorithmId<webcrypto::CreateRsaOaepAlgorithm, | |
| 89 blink::WebCryptoAlgorithmIdSha1>; | |
| 90 // TODO(padolph): The Web Crypto spec does not enumerate AES-KW 128 yet | |
| 91 map_["A128KW"] = &blink::WebCryptoAlgorithm::createNull; | |
| 92 // TODO(padolph): The Web Crypto spec does not enumerate AES-KW 256 yet | |
| 93 map_["A256KW"] = &blink::WebCryptoAlgorithm::createNull; | |
| 94 map_["A128GCM"] = | |
| 95 &BindAlgFactoryAlgorithmId<webcrypto::CreateAlgorithm, | |
| 96 blink::WebCryptoAlgorithmIdAesGcm>; | |
| 97 map_["A256GCM"] = | |
| 98 &BindAlgFactoryAlgorithmId<webcrypto::CreateAlgorithm, | |
| 99 blink::WebCryptoAlgorithmIdAesGcm>; | |
| 100 map_["A128CBC"] = | |
| 101 &BindAlgFactoryAlgorithmId<webcrypto::CreateAlgorithm, | |
| 102 blink::WebCryptoAlgorithmIdAesCbc>; | |
| 103 map_["A192CBC"] = | |
| 104 &BindAlgFactoryAlgorithmId<webcrypto::CreateAlgorithm, | |
| 105 blink::WebCryptoAlgorithmIdAesCbc>; | |
| 106 map_["A256CBC"] = | |
| 107 &BindAlgFactoryAlgorithmId<webcrypto::CreateAlgorithm, | |
| 108 blink::WebCryptoAlgorithmIdAesCbc>; | |
| 109 } | 49 } |
| 110 | 50 |
| 111 blink::WebCryptoAlgorithm CreateAlgorithmFromName(const std::string& alg_id) | 51 explicit JwkAlgorithmInfo(AlgorithmCreationFunc algorithm_creation_func) |
| 112 const { | 52 : creation_func_(algorithm_creation_func), |
| 113 const JwkAlgFactoryMap::const_iterator pos = map_.find(alg_id); | 53 required_key_length_bytes_(NO_KEY_SIZE_REQUIREMENT) { |
| 114 if (pos == map_.end()) | 54 } |
| 115 return blink::WebCryptoAlgorithm::createNull(); | 55 |
| 116 return pos->second(); | 56 JwkAlgorithmInfo(AlgorithmCreationFunc algorithm_creation_func, |
| 57 unsigned int required_key_length_bits) | |
| 58 : creation_func_(algorithm_creation_func), | |
| 59 required_key_length_bytes_(required_key_length_bits / 8) { | |
| 60 DCHECK((required_key_length_bits % 8) == 0); | |
| 61 } | |
| 62 | |
| 63 bool CreateAlgorithm(blink::WebCryptoAlgorithm* algorithm) const { | |
| 64 *algorithm = creation_func_(); | |
| 65 return !algorithm->isNull(); | |
| 66 } | |
| 67 | |
| 68 bool IsInvalidKeyByteLength(size_t byte_length) const { | |
| 69 if (required_key_length_bytes_ == NO_KEY_SIZE_REQUIREMENT) | |
| 70 return false; | |
| 71 return required_key_length_bytes_ != byte_length; | |
| 117 } | 72 } |
| 118 | 73 |
| 119 private: | 74 private: |
| 120 JwkAlgFactoryMap map_; | 75 enum {NO_KEY_SIZE_REQUIREMENT = UINT_MAX}; |
|
Ryan Sleevi
2014/02/04 02:36:35
style: spaces after { / before }
eroman
2014/02/04 02:46:03
Done.
| |
| 76 | |
| 77 AlgorithmCreationFunc creation_func_; | |
| 78 | |
| 79 // The expected key size for the algorithm or NO_KEY_SIZE_REQUIREMENT. | |
| 80 unsigned int required_key_length_bytes_; | |
| 81 | |
| 121 }; | 82 }; |
| 122 | 83 |
| 123 base::LazyInstance<JwkAlgorithmFactoryMap> jwk_alg_factory = | 84 typedef std::map<std::string, JwkAlgorithmInfo> JwkAlgorithmInfoMap; |
| 85 | |
| 86 class JwkAlgorithmRegistry { | |
| 87 public: | |
| 88 JwkAlgorithmRegistry() { | |
| 89 // TODO(eroman): | |
| 90 // http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-20 | |
| 91 // says HMAC with SHA-2 should have a key size at least as large as the | |
| 92 // hash output. | |
| 93 alg_to_info_["HS256"] = JwkAlgorithmInfo( | |
| 94 &BindAlgorithmId<webcrypto::CreateHmacAlgorithmByHashId, | |
| 95 blink::WebCryptoAlgorithmIdSha256>); | |
| 96 alg_to_info_["HS384"] = JwkAlgorithmInfo( | |
| 97 &BindAlgorithmId<webcrypto::CreateHmacAlgorithmByHashId, | |
| 98 blink::WebCryptoAlgorithmIdSha384>); | |
| 99 alg_to_info_["HS512"] = JwkAlgorithmInfo( | |
| 100 &BindAlgorithmId<webcrypto::CreateHmacAlgorithmByHashId, | |
| 101 blink::WebCryptoAlgorithmIdSha512>); | |
| 102 alg_to_info_["RS256"] = JwkAlgorithmInfo( | |
| 103 &BindAlgorithmId<webcrypto::CreateRsaSsaAlgorithm, | |
| 104 blink::WebCryptoAlgorithmIdSha256>); | |
| 105 alg_to_info_["RS384"] = JwkAlgorithmInfo( | |
| 106 &BindAlgorithmId<webcrypto::CreateRsaSsaAlgorithm, | |
| 107 blink::WebCryptoAlgorithmIdSha384>); | |
| 108 alg_to_info_["RS512"] = JwkAlgorithmInfo( | |
| 109 &BindAlgorithmId<webcrypto::CreateRsaSsaAlgorithm, | |
| 110 blink::WebCryptoAlgorithmIdSha512>); | |
| 111 alg_to_info_["RSA1_5"] = JwkAlgorithmInfo( | |
| 112 &BindAlgorithmId<webcrypto::CreateAlgorithm, | |
| 113 blink::WebCryptoAlgorithmIdRsaEsPkcs1v1_5>); | |
| 114 alg_to_info_["RSA-OAEP"] = JwkAlgorithmInfo( | |
| 115 &BindAlgorithmId<webcrypto::CreateRsaOaepAlgorithm, | |
| 116 blink::WebCryptoAlgorithmIdSha1>); | |
| 117 // TODO(padolph): The Web Crypto spec does not enumerate AES-KW 128 yet | |
| 118 alg_to_info_["A128KW"] = | |
| 119 JwkAlgorithmInfo(&blink::WebCryptoAlgorithm::createNull, 128); | |
| 120 // TODO(padolph): The Web Crypto spec does not enumerate AES-KW 256 yet | |
| 121 alg_to_info_["A256KW"] = | |
| 122 JwkAlgorithmInfo(&blink::WebCryptoAlgorithm::createNull, 256); | |
| 123 alg_to_info_["A128GCM"] = JwkAlgorithmInfo( | |
| 124 &BindAlgorithmId<webcrypto::CreateAlgorithm, | |
| 125 blink::WebCryptoAlgorithmIdAesGcm>, 128); | |
| 126 alg_to_info_["A256GCM"] = JwkAlgorithmInfo( | |
| 127 &BindAlgorithmId<webcrypto::CreateAlgorithm, | |
| 128 blink::WebCryptoAlgorithmIdAesGcm>, 256); | |
| 129 alg_to_info_["A128CBC"] = JwkAlgorithmInfo( | |
| 130 &BindAlgorithmId<webcrypto::CreateAlgorithm, | |
| 131 blink::WebCryptoAlgorithmIdAesCbc>, 128); | |
| 132 alg_to_info_["A192CBC"] = JwkAlgorithmInfo( | |
| 133 &BindAlgorithmId<webcrypto::CreateAlgorithm, | |
| 134 blink::WebCryptoAlgorithmIdAesCbc>, 192); | |
| 135 alg_to_info_["A256CBC"] = JwkAlgorithmInfo( | |
| 136 &BindAlgorithmId<webcrypto::CreateAlgorithm, | |
| 137 blink::WebCryptoAlgorithmIdAesCbc>, 256); | |
| 138 } | |
| 139 | |
| 140 // Returns NULL if the algorithm name was not registered. | |
| 141 const JwkAlgorithmInfo* GetAlgorithmInfo(const std::string& jwk_alg) const { | |
| 142 const JwkAlgorithmInfoMap::const_iterator pos = alg_to_info_.find(jwk_alg); | |
| 143 if (pos == alg_to_info_.end()) | |
| 144 return NULL; | |
| 145 return &pos->second; | |
| 146 } | |
| 147 | |
| 148 private: | |
| 149 // Binds a WebCryptoAlgorithmId value to a compatible factory function. | |
| 150 typedef blink::WebCryptoAlgorithm (*FuncWithWebCryptoAlgIdArg)( | |
| 151 blink::WebCryptoAlgorithmId); | |
| 152 template <FuncWithWebCryptoAlgIdArg func, | |
| 153 blink::WebCryptoAlgorithmId algorithm_id> | |
| 154 static blink::WebCryptoAlgorithm BindAlgorithmId() { | |
| 155 return func(algorithm_id); | |
| 156 } | |
| 157 | |
| 158 JwkAlgorithmInfoMap alg_to_info_; | |
| 159 }; | |
| 160 | |
| 161 base::LazyInstance<JwkAlgorithmRegistry> jwk_alg_registry = | |
| 124 LAZY_INSTANCE_INITIALIZER; | 162 LAZY_INSTANCE_INITIALIZER; |
| 125 | 163 |
| 126 bool WebCryptoAlgorithmsConsistent(const blink::WebCryptoAlgorithm& alg1, | 164 bool WebCryptoAlgorithmsConsistent(const blink::WebCryptoAlgorithm& alg1, |
| 127 const blink::WebCryptoAlgorithm& alg2) { | 165 const blink::WebCryptoAlgorithm& alg2) { |
| 128 DCHECK(!alg1.isNull()); | 166 DCHECK(!alg1.isNull()); |
| 129 DCHECK(!alg2.isNull()); | 167 DCHECK(!alg2.isNull()); |
| 130 if (alg1.id() != alg2.id()) | 168 if (alg1.id() != alg2.id()) |
| 131 return false; | 169 return false; |
| 132 switch (alg1.id()) { | 170 switch (alg1.id()) { |
| 133 case blink::WebCryptoAlgorithmIdHmac: | 171 case blink::WebCryptoAlgorithmIdHmac: |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 154 break; | 192 break; |
| 155 } | 193 } |
| 156 return false; | 194 return false; |
| 157 } | 195 } |
| 158 | 196 |
| 159 bool GetDecodedUrl64ValueByKey( | 197 bool GetDecodedUrl64ValueByKey( |
| 160 const base::DictionaryValue& dict, | 198 const base::DictionaryValue& dict, |
| 161 const std::string& key, | 199 const std::string& key, |
| 162 std::string* decoded) { | 200 std::string* decoded) { |
| 163 std::string value_url64; | 201 std::string value_url64; |
| 164 if (!dict.GetString(key, &value_url64) || | 202 return dict.GetString(key, &value_url64) && |
| 165 !webcrypto::Base64DecodeUrlSafe(value_url64, decoded) || | 203 webcrypto::Base64DecodeUrlSafe(value_url64, decoded); |
| 166 !decoded->size()) { | |
| 167 return false; | |
| 168 } | |
| 169 return true; | |
| 170 } | 204 } |
| 171 | 205 |
| 172 } // namespace | 206 } // namespace |
| 173 | 207 |
| 174 WebCryptoImpl::WebCryptoImpl() { | 208 WebCryptoImpl::WebCryptoImpl() { |
| 175 Init(); | 209 Init(); |
| 176 } | 210 } |
| 177 | 211 |
| 178 void WebCryptoImpl::encrypt( | 212 void WebCryptoImpl::encrypt( |
| 179 const blink::WebCryptoAlgorithm& algorithm, | 213 const blink::WebCryptoAlgorithm& algorithm, |
| (...skipping 343 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 523 // 1. JWK alg present but unrecognized: error | 557 // 1. JWK alg present but unrecognized: error |
| 524 // 2. JWK alg valid AND input algorithm isNull: use JWK value | 558 // 2. JWK alg valid AND input algorithm isNull: use JWK value |
| 525 // 3. JWK alg valid AND input algorithm specified, but JWK value | 559 // 3. JWK alg valid AND input algorithm specified, but JWK value |
| 526 // inconsistent with input: error | 560 // inconsistent with input: error |
| 527 // 4. JWK alg valid AND input algorithm specified, both consistent: use | 561 // 4. JWK alg valid AND input algorithm specified, both consistent: use |
| 528 // input value (because it has potentially more details) | 562 // input value (because it has potentially more details) |
| 529 // 5. JWK alg missing AND input algorithm isNull: error | 563 // 5. JWK alg missing AND input algorithm isNull: error |
| 530 // 6. JWK alg missing AND input algorithm specified: use input value | 564 // 6. JWK alg missing AND input algorithm specified: use input value |
| 531 // TODO(eroman): Should error if "alg" was specified but not a string. | 565 // TODO(eroman): Should error if "alg" was specified but not a string. |
| 532 blink::WebCryptoAlgorithm algorithm = blink::WebCryptoAlgorithm::createNull(); | 566 blink::WebCryptoAlgorithm algorithm = blink::WebCryptoAlgorithm::createNull(); |
| 567 const JwkAlgorithmInfo* algorithm_info = NULL; | |
| 533 std::string jwk_alg_value; | 568 std::string jwk_alg_value; |
| 534 if (dict_value->GetString("alg", &jwk_alg_value)) { | 569 if (dict_value->GetString("alg", &jwk_alg_value)) { |
| 535 // JWK alg present | 570 // JWK alg present |
| 536 | 571 |
| 537 // TODO(padolph): Validate alg vs kty. For example kty="RSA" implies alg can | 572 // TODO(padolph): Validate alg vs kty. For example kty="RSA" implies alg can |
| 538 // only be from the RSA family. | 573 // only be from the RSA family. |
| 539 | 574 |
| 540 const blink::WebCryptoAlgorithm jwk_algorithm = | 575 blink::WebCryptoAlgorithm jwk_algorithm = |
| 541 jwk_alg_factory.Get().CreateAlgorithmFromName(jwk_alg_value); | 576 blink::WebCryptoAlgorithm::createNull(); |
| 542 if (jwk_algorithm.isNull()) { | 577 algorithm_info = jwk_alg_registry.Get().GetAlgorithmInfo(jwk_alg_value); |
| 543 // JWK alg unrecognized | 578 if (!algorithm_info || !algorithm_info->CreateAlgorithm(&jwk_algorithm)) |
| 544 return Status::ErrorJwkUnrecognizedAlgorithm(); // case 1 | 579 return Status::ErrorJwkUnrecognizedAlgorithm(); // case 1 |
| 545 } | 580 |
| 546 // JWK alg valid | 581 // JWK alg valid |
| 547 if (algorithm_or_null.isNull()) { | 582 if (algorithm_or_null.isNull()) { |
| 548 // input algorithm not specified | 583 // input algorithm not specified |
| 549 algorithm = jwk_algorithm; // case 2 | 584 algorithm = jwk_algorithm; // case 2 |
| 550 } else { | 585 } else { |
| 551 // input algorithm specified | 586 // input algorithm specified |
| 552 if (!WebCryptoAlgorithmsConsistent(jwk_algorithm, algorithm_or_null)) | 587 if (!WebCryptoAlgorithmsConsistent(jwk_algorithm, algorithm_or_null)) |
| 553 return Status::ErrorJwkAlgorithmInconsistent(); // case 3 | 588 return Status::ErrorJwkAlgorithmInconsistent(); // case 3 |
| 554 algorithm = algorithm_or_null; // case 4 | 589 algorithm = algorithm_or_null; // case 4 |
| 555 } | 590 } |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 584 } | 619 } |
| 585 } | 620 } |
| 586 | 621 |
| 587 // JWK keying material --> ImportKeyInternal() | 622 // JWK keying material --> ImportKeyInternal() |
| 588 if (jwk_kty_value == "oct") { | 623 if (jwk_kty_value == "oct") { |
| 589 | 624 |
| 590 std::string jwk_k_value; | 625 std::string jwk_k_value; |
| 591 if (!GetDecodedUrl64ValueByKey(*dict_value, "k", &jwk_k_value)) | 626 if (!GetDecodedUrl64ValueByKey(*dict_value, "k", &jwk_k_value)) |
| 592 return Status::ErrorJwkDecodeK(); | 627 return Status::ErrorJwkDecodeK(); |
| 593 | 628 |
| 594 // TODO(padolph): Some JWK alg ID's embed information about the key length | 629 // Some JWK alg ID's embed information about the key length in the alg ID |
| 595 // in the alg ID string. For example "A128" implies the JWK carries 128 bits | 630 // string. For example "A128CBC" implies the JWK carries 128 bits |
| 596 // of key material, and "HS512" implies the JWK carries _at least_ 512 bits | 631 // of key material. For such keys validate that enough bytes were provided. |
| 597 // of key material. For such keys validate the actual key length against the | 632 // If this validation is not done, then it would be possible to select a |
| 598 // value in the ID. | 633 // different algorithm by passing a different lengthed key, since that is |
| 634 // how WebCrypto interprets things. | |
| 635 if (algorithm_info && | |
| 636 algorithm_info->IsInvalidKeyByteLength(jwk_k_value.size())) { | |
| 637 return Status::ErrorJwkIncorrectKeyLength(); | |
| 638 } | |
| 599 | 639 |
| 600 return ImportKeyInternal(blink::WebCryptoKeyFormatRaw, | 640 return ImportKeyInternal(blink::WebCryptoKeyFormatRaw, |
| 601 reinterpret_cast<const uint8*>(jwk_k_value.data()), | 641 reinterpret_cast<const uint8*>(jwk_k_value.data()), |
| 602 jwk_k_value.size(), | 642 jwk_k_value.size(), |
| 603 algorithm, | 643 algorithm, |
| 604 extractable, | 644 extractable, |
| 605 usage_mask, | 645 usage_mask, |
| 606 key); | 646 key); |
| 607 } else if (jwk_kty_value == "RSA") { | 647 } else if (jwk_kty_value == "RSA") { |
| 608 | 648 |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 636 key); | 676 key); |
| 637 | 677 |
| 638 } else { | 678 } else { |
| 639 return Status::ErrorJwkUnrecognizedKty(); | 679 return Status::ErrorJwkUnrecognizedKty(); |
| 640 } | 680 } |
| 641 | 681 |
| 642 return Status::Success(); | 682 return Status::Success(); |
| 643 } | 683 } |
| 644 | 684 |
| 645 } // namespace content | 685 } // namespace content |
| OLD | NEW |