| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/api/certificate_provider/certificate_provider_api.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/values.h" |
| #include "chrome/browser/certificate_provider/certificate_provider_service.h" |
| #include "chrome/browser/certificate_provider/certificate_provider_service_factory.h" |
| #include "chrome/browser/certificate_provider/pin_dialog_manager.h" |
| #include "chrome/browser/certificate_provider/security_token_pin_dialog_host.h" |
| #include "chrome/common/extensions/api/certificate_provider.h" |
| #include "chrome/common/extensions/api/certificate_provider_internal.h" |
| #include "chromeos/components/security_token_pin/constants.h" |
| #include "extensions/browser/quota_service.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/ssl/ssl_private_key.h" |
| #include "third_party/blink/public/mojom/devtools/console_message.mojom.h" |
| #include "third_party/boringssl/src/include/openssl/ssl.h" |
| |
| namespace { |
| |
| namespace api_cp = ::extensions::api::certificate_provider; |
| namespace api_cpi = ::extensions::api::certificate_provider_internal; |
| using PinCodeType = ::chromeos::security_token_pin::CodeType; |
| using PinErrorLabel = ::chromeos::security_token_pin::ErrorLabel; |
| using RequestPinResult = ::chromeos::PinDialogManager::RequestPinResult; |
| using StopPinRequestResult = ::chromeos::PinDialogManager::StopPinRequestResult; |
| |
| PinErrorLabel GetErrorLabelForDialog(api_cp::PinRequestErrorType error_type) { |
| switch (error_type) { |
| case api_cp::PinRequestErrorType::PIN_REQUEST_ERROR_TYPE_INVALID_PIN: |
| return PinErrorLabel::kInvalidPin; |
| case api_cp::PinRequestErrorType::PIN_REQUEST_ERROR_TYPE_INVALID_PUK: |
| return PinErrorLabel::kInvalidPuk; |
| case api_cp::PinRequestErrorType:: |
| PIN_REQUEST_ERROR_TYPE_MAX_ATTEMPTS_EXCEEDED: |
| return PinErrorLabel::kMaxAttemptsExceeded; |
| case api_cp::PinRequestErrorType::PIN_REQUEST_ERROR_TYPE_UNKNOWN_ERROR: |
| return PinErrorLabel::kUnknown; |
| case api_cp::PinRequestErrorType::PIN_REQUEST_ERROR_TYPE_NONE: |
| return PinErrorLabel::kNone; |
| } |
| |
| NOTREACHED(); |
| return PinErrorLabel::kNone; |
| } |
| |
| } // namespace |
| |
| namespace extensions { |
| |
| namespace { |
| |
| const char kCertificateProviderErrorEmptyChain[] = |
| "Certificate chain is empty."; |
| const char kCertificateProviderErrorChainTooLong[] = |
| "Certificate chain should contain exactly one item."; |
| const char kCertificateProviderErrorInvalidX509Cert[] = |
| "Certificate is not a valid X.509 certificate."; |
| const char kCertificateProviderErrorECDSANotSupported[] = |
| "Key type ECDSA not supported."; |
| const char kCertificateProviderErrorUnknownKeyType[] = "Key type unknown."; |
| const char kCertificateProviderErrorAborted[] = "Request was aborted."; |
| const char kCertificateProviderErrorTimeout[] = |
| "Request timed out, reply rejected."; |
| const char kCertificateProviderErrorInvalidId[] = "Invalid requestId"; |
| const char kCertificateProviderErrorUnexpectedError[] = |
| "Error supplied with non-empty data."; |
| const char kCertificateProviderErrorNeitherResultNorError[] = |
| "Neither the result nor an error supplied."; |
| const char kCertificateProviderErrorNoAlgorithms[] = "Algorithm list is empty."; |
| |
| // requestPin constants. |
| const char kCertificateProviderNoActiveDialog[] = |
| "No active dialog from extension."; |
| const char kCertificateProviderInvalidSignId[] = "Invalid signRequestId"; |
| const char kCertificateProviderInvalidAttemptsLeft[] = "Invalid attemptsLeft"; |
| const char kCertificateProviderOtherFlowInProgress[] = "Other flow in progress"; |
| const char kCertificateProviderPreviousDialogActive[] = |
| "Previous request not finished"; |
| const char kCertificateProviderNoUserInput[] = "No user input received"; |
| |
| // The BucketMapper implementation for the requestPin API that avoids using the |
| // quota when the current request uses the requestId that is strictly greater |
| // than all previous ones. |
| class RequestPinExceptFirstQuotaBucketMapper final |
| : public QuotaLimitHeuristic::BucketMapper { |
| public: |
| RequestPinExceptFirstQuotaBucketMapper() = default; |
| RequestPinExceptFirstQuotaBucketMapper( |
| const RequestPinExceptFirstQuotaBucketMapper&) = delete; |
| RequestPinExceptFirstQuotaBucketMapper& operator=( |
| const RequestPinExceptFirstQuotaBucketMapper&) = delete; |
| ~RequestPinExceptFirstQuotaBucketMapper() override = default; |
| |
| void GetBucketsForArgs(const base::Value::List& args, |
| QuotaLimitHeuristic::BucketList* buckets) override { |
| if (args.empty()) |
| return; |
| const base::Value::Dict* details = args.front().GetIfDict(); |
| if (!details) |
| return; |
| absl::optional<int> sign_request_id = details->FindInt("signRequestId"); |
| if (!sign_request_id.has_value()) |
| return; |
| if (*sign_request_id > biggest_request_id_) { |
| // Either it's the first request with the newly issued requestId, or it's |
| // an invalid requestId (bigger than the real one). Return a new bucket in |
| // order to apply no quota for the former case; for the latter case the |
| // quota doesn't matter much, except that we're maybe making it stricter |
| // for future requests (which is bearable). |
| biggest_request_id_ = *sign_request_id; |
| new_request_bucket_ = std::make_unique<QuotaLimitHeuristic::Bucket>(); |
| buckets->push_back(new_request_bucket_.get()); |
| return; |
| } |
| // Either it's a repeatitive request for the given requestId, or the |
| // extension reordered the requests. Fall back to the default bucket (shared |
| // between all requests) in that case. |
| buckets->push_back(&default_bucket_); |
| } |
| |
| private: |
| int biggest_request_id_ = -1; |
| QuotaLimitHeuristic::Bucket default_bucket_; |
| std::unique_ptr<QuotaLimitHeuristic::Bucket> new_request_bucket_; |
| }; |
| |
| scoped_refptr<net::X509Certificate> ParseCertificateDer( |
| const std::vector<uint8_t>& cert_der, |
| std::string* out_error_message) { |
| if (cert_der.empty()) { |
| *out_error_message = kCertificateProviderErrorInvalidX509Cert; |
| return nullptr; |
| } |
| |
| // Allow UTF-8 inside PrintableStrings in client certificates. See |
| // crbug.com/770323 and crbug.com/788655. |
| net::X509Certificate::UnsafeCreateOptions options; |
| options.printable_string_is_utf8 = true; |
| scoped_refptr<net::X509Certificate> certificate = |
| net::X509Certificate::CreateFromBytesUnsafeOptions(cert_der, options); |
| if (!certificate) { |
| *out_error_message = kCertificateProviderErrorInvalidX509Cert; |
| return nullptr; |
| } |
| |
| size_t public_key_length_in_bits = 0; |
| net::X509Certificate::PublicKeyType type = |
| net::X509Certificate::kPublicKeyTypeUnknown; |
| net::X509Certificate::GetPublicKeyInfo(certificate->cert_buffer(), |
| &public_key_length_in_bits, &type); |
| |
| switch (type) { |
| case net::X509Certificate::kPublicKeyTypeRSA: |
| break; |
| case net::X509Certificate::kPublicKeyTypeECDSA: |
| *out_error_message = kCertificateProviderErrorECDSANotSupported; |
| return nullptr; |
| case net::X509Certificate::kPublicKeyTypeUnknown: |
| case net::X509Certificate::kPublicKeyTypeDSA: |
| case net::X509Certificate::kPublicKeyTypeDH: |
| case net::X509Certificate::kPublicKeyTypeECDH: |
| *out_error_message = kCertificateProviderErrorUnknownKeyType; |
| return nullptr; |
| } |
| return certificate; |
| } |
| |
| bool ParseCertificateInfo( |
| const api_cp::CertificateInfo& info, |
| chromeos::certificate_provider::CertificateInfo* out_info, |
| std::string* out_error_message) { |
| out_info->certificate = |
| ParseCertificateDer(info.certificate, out_error_message); |
| if (!out_info->certificate) |
| return false; |
| |
| out_info->supported_algorithms.reserve(info.supported_hashes.size()); |
| for (const api_cp::Hash hash : info.supported_hashes) { |
| switch (hash) { |
| case api_cp::HASH_MD5_SHA1: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_MD5_SHA1); |
| break; |
| case api_cp::HASH_SHA1: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA1); |
| break; |
| case api_cp::HASH_SHA256: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA256); |
| break; |
| case api_cp::HASH_SHA384: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA384); |
| break; |
| case api_cp::HASH_SHA512: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA512); |
| break; |
| case api_cp::HASH_NONE: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| if (out_info->supported_algorithms.empty()) { |
| *out_error_message = kCertificateProviderErrorNoAlgorithms; |
| return false; |
| } |
| return true; |
| } |
| |
| bool ParseClientCertificateInfo( |
| const api_cp::ClientCertificateInfo& info, |
| chromeos::certificate_provider::CertificateInfo* out_info, |
| std::string* out_error_message) { |
| if (info.certificate_chain.empty()) { |
| *out_error_message = kCertificateProviderErrorEmptyChain; |
| return false; |
| } |
| if (info.certificate_chain.size() > 1) { |
| // TODO(crbug.com/1101854): Support passing certificate chains. |
| *out_error_message = kCertificateProviderErrorChainTooLong; |
| return false; |
| } |
| out_info->certificate = |
| ParseCertificateDer(info.certificate_chain[0], out_error_message); |
| if (!out_info->certificate) |
| return false; |
| |
| out_info->supported_algorithms.reserve(info.supported_algorithms.size()); |
| for (const api_cp::Algorithm algorithm : info.supported_algorithms) { |
| switch (algorithm) { |
| case api_cp::ALGORITHM_RSASSA_PKCS1_V1_5_MD5_SHA1: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_MD5_SHA1); |
| break; |
| case api_cp::ALGORITHM_RSASSA_PKCS1_V1_5_SHA1: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA1); |
| break; |
| case api_cp::ALGORITHM_RSASSA_PKCS1_V1_5_SHA256: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA256); |
| break; |
| case api_cp::ALGORITHM_RSASSA_PKCS1_V1_5_SHA384: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA384); |
| break; |
| case api_cp::ALGORITHM_RSASSA_PKCS1_V1_5_SHA512: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PKCS1_SHA512); |
| break; |
| case api_cp::ALGORITHM_RSASSA_PSS_SHA256: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PSS_RSAE_SHA256); |
| break; |
| case api_cp::ALGORITHM_RSASSA_PSS_SHA384: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PSS_RSAE_SHA384); |
| break; |
| case api_cp::ALGORITHM_RSASSA_PSS_SHA512: |
| out_info->supported_algorithms.push_back(SSL_SIGN_RSA_PSS_RSAE_SHA512); |
| break; |
| case api_cp::ALGORITHM_NONE: |
| NOTREACHED(); |
| return false; |
| } |
| } |
| if (out_info->supported_algorithms.empty()) { |
| *out_error_message = kCertificateProviderErrorNoAlgorithms; |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| const int api::certificate_provider::kMaxClosedDialogsPerMinute = 10; |
| const int api::certificate_provider::kMaxClosedDialogsPer10Minutes = 30; |
| |
| CertificateProviderInternalReportCertificatesFunction:: |
| ~CertificateProviderInternalReportCertificatesFunction() {} |
| |
| ExtensionFunction::ResponseAction |
| CertificateProviderInternalReportCertificatesFunction::Run() { |
| std::unique_ptr<api_cpi::ReportCertificates::Params> params( |
| api_cpi::ReportCertificates::Params::Create(args())); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| chromeos::CertificateProviderService* const service = |
| chromeos::CertificateProviderServiceFactory::GetForBrowserContext( |
| browser_context()); |
| DCHECK(service); |
| |
| if (!params->certificates) { |
| // In the public API, the certificates parameter is mandatory. We only run |
| // into this case, if the custom binding rejected the reply by the |
| // extension. |
| return RespondNow(Error(kCertificateProviderErrorAborted)); |
| } |
| |
| chromeos::certificate_provider::CertificateInfoList cert_infos; |
| std::vector<std::vector<uint8_t>> rejected_certificates; |
| for (const api_cp::CertificateInfo& input_cert_info : *params->certificates) { |
| chromeos::certificate_provider::CertificateInfo parsed_cert_info; |
| std::string error_message; |
| if (ParseCertificateInfo(input_cert_info, &parsed_cert_info, |
| &error_message)) { |
| cert_infos.push_back(parsed_cert_info); |
| } else { |
| rejected_certificates.push_back(input_cert_info.certificate); |
| WriteToConsole(blink::mojom::ConsoleMessageLevel::kError, error_message); |
| } |
| } |
| |
| // TODO(crbug.com/1046860): Remove logging after stabilizing the feature. |
| LOG(WARNING) << "Certificates provided by extension " << extension()->id() |
| << ": " << cert_infos.size() << ", rejected " |
| << rejected_certificates.size(); |
| |
| service->SetCertificatesProvidedByExtension(extension_id(), cert_infos); |
| |
| if (service->SetExtensionCertificateReplyReceived(extension_id(), |
| params->request_id)) |
| return RespondNow(ArgumentList( |
| api_cpi::ReportCertificates::Results::Create(rejected_certificates))); |
| |
| // The custom binding already checks for multiple reports to the same |
| // request. The only remaining case, why this reply can fail is that the |
| // request timed out. |
| return RespondNow(Error(kCertificateProviderErrorTimeout)); |
| } |
| |
| CertificateProviderStopPinRequestFunction:: |
| ~CertificateProviderStopPinRequestFunction() = default; |
| |
| ExtensionFunction::ResponseAction |
| CertificateProviderStopPinRequestFunction::Run() { |
| std::unique_ptr<api_cp::StopPinRequest::Params> params( |
| api_cp::StopPinRequest::Params::Create(args())); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| // TODO(crbug.com/1046860): Remove logging after stabilizing the feature. |
| LOG(WARNING) << "Handling PIN stop request from extension " |
| << extension()->id() << " error " << params->details.error_type; |
| |
| chromeos::CertificateProviderService* const service = |
| chromeos::CertificateProviderServiceFactory::GetForBrowserContext( |
| browser_context()); |
| DCHECK(service); |
| if (params->details.error_type == |
| api_cp::PinRequestErrorType::PIN_REQUEST_ERROR_TYPE_NONE) { |
| bool dialog_closed = |
| service->pin_dialog_manager()->CloseDialog(extension_id()); |
| if (!dialog_closed) { |
| // This might happen if the user closed the dialog while extension was |
| // processing the input. |
| // TODO(crbug.com/1046860): Remove logging after stabilizing the feature. |
| LOG(WARNING) << "PIN stop request failed: " |
| << kCertificateProviderNoActiveDialog; |
| return RespondNow(Error(kCertificateProviderNoActiveDialog)); |
| } |
| |
| // TODO(crbug.com/1046860): Remove logging after stabilizing the feature. |
| LOG(WARNING) << "PIN stop request succeeded"; |
| return RespondNow(NoArguments()); |
| } |
| |
| // Extension provided an error, which means it intends to notify the user with |
| // the error and not allow any more input. |
| const PinErrorLabel error_label = |
| GetErrorLabelForDialog(params->details.error_type); |
| const StopPinRequestResult stop_request_result = |
| service->pin_dialog_manager()->StopPinRequestWithError( |
| extension()->id(), error_label, |
| base::BindOnce( |
| &CertificateProviderStopPinRequestFunction::OnPinRequestStopped, |
| this)); |
| std::string error_result; |
| switch (stop_request_result) { |
| case StopPinRequestResult::kNoActiveDialog: |
| error_result = kCertificateProviderNoActiveDialog; |
| break; |
| case StopPinRequestResult::kNoUserInput: |
| error_result = kCertificateProviderNoUserInput; |
| break; |
| case StopPinRequestResult::kSuccess: |
| return RespondLater(); |
| } |
| // TODO(crbug.com/1046860): Remove logging after stabilizing the feature. |
| LOG(WARNING) << "PIN stop request failed: " << error_result; |
| return RespondNow(Error(std::move(error_result))); |
| } |
| |
| void CertificateProviderStopPinRequestFunction::OnPinRequestStopped() { |
| // TODO(crbug.com/1046860): Remove logging after stabilizing the feature. |
| LOG(WARNING) << "PIN stop request succeeded"; |
| Respond(NoArguments()); |
| } |
| |
| CertificateProviderRequestPinFunction:: |
| ~CertificateProviderRequestPinFunction() {} |
| |
| bool CertificateProviderRequestPinFunction::ShouldSkipQuotaLimiting() const { |
| chromeos::CertificateProviderService* const service = |
| chromeos::CertificateProviderServiceFactory::GetForBrowserContext( |
| browser_context()); |
| DCHECK(service); |
| |
| return !service->pin_dialog_manager()->LastPinDialogClosed(extension_id()); |
| } |
| |
| void CertificateProviderRequestPinFunction::GetQuotaLimitHeuristics( |
| QuotaLimitHeuristics* heuristics) const { |
| // Apply a 1-minute and a 10-minute quotas. A special bucket mapper is used in |
| // order to, approximately, skip applying quotas to the first request for each |
| // requestId (such logic cannot be done in ShouldSkipQuotaLimiting(), since |
| // it's not called with the request's parameters). The limitation constants |
| // are decremented below to account the first request. |
| |
| QuotaLimitHeuristic::Config short_limit_config = { |
| api::certificate_provider::kMaxClosedDialogsPerMinute - 1, |
| base::Minutes(1)}; |
| heuristics->push_back(std::make_unique<QuotaService::TimedLimit>( |
| short_limit_config, |
| std::make_unique<RequestPinExceptFirstQuotaBucketMapper>(), |
| "MAX_PIN_DIALOGS_CLOSED_PER_MINUTE")); |
| |
| QuotaLimitHeuristic::Config long_limit_config = { |
| api::certificate_provider::kMaxClosedDialogsPer10Minutes - 1, |
| base::Minutes(10)}; |
| heuristics->push_back(std::make_unique<QuotaService::TimedLimit>( |
| long_limit_config, |
| std::make_unique<RequestPinExceptFirstQuotaBucketMapper>(), |
| "MAX_PIN_DIALOGS_CLOSED_PER_10_MINUTES")); |
| } |
| |
| ExtensionFunction::ResponseAction CertificateProviderRequestPinFunction::Run() { |
| std::unique_ptr<api_cp::RequestPin::Params> params( |
| api_cp::RequestPin::Params::Create(args())); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| const api_cp::PinRequestType pin_request_type = |
| params->details.request_type == |
| api_cp::PinRequestType::PIN_REQUEST_TYPE_NONE |
| ? api_cp::PinRequestType::PIN_REQUEST_TYPE_PIN |
| : params->details.request_type; |
| |
| const PinErrorLabel error_label = |
| GetErrorLabelForDialog(params->details.error_type); |
| |
| const PinCodeType code_type = |
| (pin_request_type == api_cp::PinRequestType::PIN_REQUEST_TYPE_PIN) |
| ? PinCodeType::kPin |
| : PinCodeType::kPuk; |
| |
| chromeos::CertificateProviderService* const service = |
| chromeos::CertificateProviderServiceFactory::GetForBrowserContext( |
| browser_context()); |
| DCHECK(service); |
| |
| int attempts_left = -1; |
| if (params->details.attempts_left) { |
| if (*params->details.attempts_left < 0) |
| return RespondNow(Error(kCertificateProviderInvalidAttemptsLeft)); |
| attempts_left = *params->details.attempts_left; |
| } |
| |
| // TODO(crbug.com/1046860): Remove logging after stabilizing the feature. |
| LOG(WARNING) << "Starting PIN request from extension " << extension()->id() |
| << " signRequestId " << params->details.sign_request_id |
| << " type " << params->details.request_type << " error " |
| << params->details.error_type << " attempts " << attempts_left; |
| |
| const RequestPinResult result = service->pin_dialog_manager()->RequestPin( |
| extension()->id(), extension()->name(), params->details.sign_request_id, |
| code_type, error_label, attempts_left, |
| base::BindOnce(&CertificateProviderRequestPinFunction::OnInputReceived, |
| this)); |
| std::string error_result; |
| switch (result) { |
| case RequestPinResult::kSuccess: |
| return RespondLater(); |
| case RequestPinResult::kInvalidId: |
| error_result = kCertificateProviderInvalidSignId; |
| break; |
| case RequestPinResult::kOtherFlowInProgress: |
| error_result = kCertificateProviderOtherFlowInProgress; |
| break; |
| case RequestPinResult::kDialogDisplayedAlready: |
| error_result = kCertificateProviderPreviousDialogActive; |
| break; |
| } |
| // TODO(crbug.com/1046860): Remove logging after stabilizing the feature. |
| LOG(WARNING) << "PIN request failed: " << error_result; |
| return RespondNow(Error(std::move(error_result))); |
| } |
| |
| void CertificateProviderRequestPinFunction::OnInputReceived( |
| const std::string& value) { |
| base::Value::List create_results; |
| chromeos::CertificateProviderService* const service = |
| chromeos::CertificateProviderServiceFactory::GetForBrowserContext( |
| browser_context()); |
| DCHECK(service); |
| if (!value.empty()) { |
| // TODO(crbug.com/1046860): Remove logging after stabilizing the feature. |
| LOG(WARNING) << "PIN request succeeded"; |
| api::certificate_provider::PinResponseDetails details; |
| details.user_input = value; |
| create_results.Append(details.ToValue()); |
| } else { |
| // TODO(crbug.com/1046860): Remove logging after stabilizing the feature. |
| LOG(WARNING) << "PIN request canceled"; |
| } |
| |
| Respond(ArgumentList(std::move(create_results))); |
| } |
| |
| CertificateProviderSetCertificatesFunction:: |
| ~CertificateProviderSetCertificatesFunction() = default; |
| |
| ExtensionFunction::ResponseAction |
| CertificateProviderSetCertificatesFunction::Run() { |
| std::unique_ptr<api_cp::SetCertificates::Params> params( |
| api_cp::SetCertificates::Params::Create(args())); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| if (!params->details.client_certificates.empty() && params->details.error) { |
| return RespondNow(Error(kCertificateProviderErrorUnexpectedError)); |
| } |
| |
| chromeos::certificate_provider::CertificateInfoList accepted_certificates; |
| uint32_t rejected_certificates_count = 0; |
| for (const api_cp::ClientCertificateInfo& input_cert_info : |
| params->details.client_certificates) { |
| chromeos::certificate_provider::CertificateInfo parsed_cert_info; |
| std::string parsing_error_message; |
| if (ParseClientCertificateInfo(input_cert_info, &parsed_cert_info, |
| &parsing_error_message)) { |
| accepted_certificates.push_back(parsed_cert_info); |
| } else { |
| rejected_certificates_count++; |
| WriteToConsole(blink::mojom::ConsoleMessageLevel::kError, |
| parsing_error_message); |
| } |
| } |
| |
| // TODO(crbug.com/1046860): Remove logging after stabilizing the feature. |
| LOG(WARNING) << "Certificates provided by extension " << extension()->id() |
| << ": " << accepted_certificates.size() << ", rejected " |
| << rejected_certificates_count; |
| |
| chromeos::CertificateProviderService* const service = |
| chromeos::CertificateProviderServiceFactory::GetForBrowserContext( |
| browser_context()); |
| DCHECK(service); |
| service->SetCertificatesProvidedByExtension(extension_id(), |
| accepted_certificates); |
| |
| if (params->details.certificates_request_id && |
| !service->SetExtensionCertificateReplyReceived( |
| extension_id(), *params->details.certificates_request_id)) { |
| // The extension supplied invalid request ID: it could be an unknown value, |
| // or a value that was already reported before, or the request timed out. |
| return RespondNow(Error(kCertificateProviderErrorInvalidId)); |
| } |
| |
| return RespondNow(NoArguments()); |
| } |
| |
| CertificateProviderInternalReportSignatureFunction:: |
| ~CertificateProviderInternalReportSignatureFunction() {} |
| |
| ExtensionFunction::ResponseAction |
| CertificateProviderInternalReportSignatureFunction::Run() { |
| std::unique_ptr<api_cpi::ReportSignature::Params> params( |
| api_cpi::ReportSignature::Params::Create(args())); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| chromeos::CertificateProviderService* const service = |
| chromeos::CertificateProviderServiceFactory::GetForBrowserContext( |
| browser_context()); |
| DCHECK(service); |
| |
| std::vector<uint8_t> signature; |
| // If an error occurred, |signature| will not be set. |
| if (params->signature) |
| signature.assign(params->signature->begin(), params->signature->end()); |
| |
| if (!service->ReplyToSignRequest(extension_id(), params->request_id, |
| signature)) { |
| // The request was aborted before, or the extension managed to bypass the |
| // checks in the API bindings and specified a bad or an already used id. |
| DLOG(WARNING) << "Unexpected reply of extension " << extension_id() |
| << " to sign request " << params->request_id; |
| } |
| return RespondNow(NoArguments()); |
| } |
| |
| CertificateProviderReportSignatureFunction:: |
| ~CertificateProviderReportSignatureFunction() = default; |
| |
| ExtensionFunction::ResponseAction |
| CertificateProviderReportSignatureFunction::Run() { |
| std::unique_ptr<api_cp::ReportSignature::Params> params( |
| api_cp::ReportSignature::Params::Create(args())); |
| EXTENSION_FUNCTION_VALIDATE(params); |
| |
| if (params->details.signature && !params->details.signature->empty() && |
| params->details.error) { |
| return RespondNow(Error(kCertificateProviderErrorUnexpectedError)); |
| } |
| if ((!params->details.signature || params->details.signature->empty()) && |
| !params->details.error) { |
| // It's not allowed to supply empty result without an error code. |
| return RespondNow(Error(kCertificateProviderErrorNeitherResultNorError)); |
| } |
| |
| chromeos::CertificateProviderService* const service = |
| chromeos::CertificateProviderServiceFactory::GetForBrowserContext( |
| browser_context()); |
| DCHECK(service); |
| |
| std::vector<uint8_t> signature; |
| // If an error occurred, |signature| will not be set. |
| if (params->details.signature) { |
| signature.assign(params->details.signature->begin(), |
| params->details.signature->end()); |
| } |
| |
| if (!service->ReplyToSignRequest( |
| extension_id(), params->details.sign_request_id, signature)) { |
| return RespondNow(Error(kCertificateProviderInvalidSignId)); |
| } |
| return RespondNow(NoArguments()); |
| } |
| |
| } // namespace extensions |