diff --git a/app_check/CMakeLists.txt b/app_check/CMakeLists.txt index b3770dc37e..e95a6f7a25 100644 --- a/app_check/CMakeLists.txt +++ b/app_check/CMakeLists.txt @@ -57,6 +57,35 @@ set(ios_SRCS src/stub/safety_net_provider_stub.cc ) +# Flatbuffer resource files used by desktop +binary_to_array("debug_token_request_resource" + "${CMAKE_CURRENT_LIST_DIR}/src/desktop/debug_token_request.fbs" + "firebase::app_check" + "${FIREBASE_GEN_FILE_DIR}/app_check") +binary_to_array("token_response_resource" + "${CMAKE_CURRENT_LIST_DIR}/src/desktop/token_response.fbs" + "firebase::app_check" + "${FIREBASE_GEN_FILE_DIR}/app_check") + +# Build the generated header from the flatbuffer schema files. +set(FLATBUFFERS_FLATC_SCHEMA_EXTRA_ARGS + "--no-union-value-namespacing" + "--gen-object-api" + "--cpp-ptr-type" "flatbuffers::unique_ptr") +set(flatbuffer_schemas + ${CMAKE_CURRENT_LIST_DIR}/src/desktop/debug_token_request.fbs + ${CMAKE_CURRENT_LIST_DIR}/src/desktop/token_response.fbs) +# Because of a bug in the version of Flatbuffers we are pinned to, +# additional flags need to be set. +set(FLATC_ARGS "${FLATBUFFERS_FLATC_SCHEMA_EXTRA_ARGS}") +build_flatbuffers("${flatbuffer_schemas}" + "" + "app_check_generate_fbs" + "${FIREBASE_FLATBUFFERS_DEPENDENCIES}" + "${FIREBASE_GEN_FILE_DIR}/app_check" + "" + "") + # Source files used by the desktop implementation. set(desktop_SRCS src/desktop/app_check_desktop.cc @@ -64,6 +93,14 @@ set(desktop_SRCS # Supported providers src/desktop/debug_provider_desktop.cc src/desktop/debug_provider_desktop.h + src/desktop/debug_token_request.h + ${debug_token_request_resource_source} + ${debug_token_request_resource_header} + ${FIREBASE_GEN_FILE_DIR}/app_check/debug_token_request_generated.h + src/desktop/token_response.h + ${token_response_resource_source} + ${token_response_resource_header} + ${FIREBASE_GEN_FILE_DIR}/app_check/token_response_generated.h # Unsupported providers src/stub/app_attest_provider_stub.cc src/stub/device_check_provider_stub.cc diff --git a/app_check/src/desktop/debug_provider_desktop.cc b/app_check/src/desktop/debug_provider_desktop.cc index 242478a072..8ad3d1ea46 100644 --- a/app_check/src/desktop/debug_provider_desktop.cc +++ b/app_check/src/desktop/debug_provider_desktop.cc @@ -14,19 +14,118 @@ #include "app_check/src/desktop/debug_provider_desktop.h" +#include +#include +#include + +#include "app/rest/response.h" +#include "app/rest/transport_builder.h" +#include "app/rest/transport_curl.h" +#include "app/rest/util.h" +#include "app/src/log.h" +#include "app/src/scheduler.h" +#include "app_check/src/desktop/debug_token_request.h" +#include "app_check/src/desktop/token_response.h" #include "firebase/app_check/debug_provider.h" namespace firebase { namespace app_check { namespace internal { -DebugAppCheckProviderFactoryInternal::DebugAppCheckProviderFactoryInternal() {} +class DebugAppCheckProvider : public AppCheckProvider { + public: + explicit DebugAppCheckProvider(App* app); + ~DebugAppCheckProvider() override; + + void GetToken(std::function + completion_callback) override; + + private: + App* app_; + + scheduler::Scheduler scheduler_; +}; + +DebugAppCheckProvider::DebugAppCheckProvider(App* app) + : app_(app), scheduler_() { + firebase::rest::util::Initialize(); + firebase::rest::InitTransportCurl(); +} + +DebugAppCheckProvider::~DebugAppCheckProvider() { + firebase::rest::CleanupTransportCurl(); + firebase::rest::util::Terminate(); +} + +// Performs the given rest request, and calls the callback based on the +// response. +void GetTokenAsync(SharedPtr request, + std::function + completion_callback) { + TokenResponse response; + firebase::rest::CreateTransport()->Perform(*request, &response); -DebugAppCheckProviderFactoryInternal::~DebugAppCheckProviderFactoryInternal() {} + if (response.status() == firebase::rest::util::HttpSuccess) { + // Call the callback with the response token + AppCheckToken token; + token.token = std::move(response.token()); + // Expected response is in seconds + int64_t extra_time = strtol(response.ttl().c_str(), nullptr, 10); + token.expire_time_millis = (response.fetch_time() + extra_time) * 1000; + completion_callback(token, kAppCheckErrorNone, ""); + } else { + // Create an error message, and pass it along instead. + AppCheckToken token; + char error_message[1000]; + snprintf(error_message, sizeof(error_message), + "The server responded with an error.\n" + "HTTP status code: %d \n" + "Response body: %s\n", + response.status(), response.GetBody()); + completion_callback(token, kAppCheckErrorUnknown, error_message); + } +} + +void DebugAppCheckProvider::GetToken( + std::function + completion_callback) { + // Identify the user's debug token + // TODO(amaurice): For now uses an environment variable, but should use other + // options. + const char* debug_token = std::getenv("APP_CHECK_DEBUG_TOKEN"); + + // Exchange debug token with the backend to get a proper attestation token. + auto request = MakeShared(app_); + request->SetDebugToken(debug_token); + + // Use an async call, since we don't want to block on the server response. + auto async_call = + callback::NewCallback(GetTokenAsync, request, completion_callback); + scheduler_.Schedule(async_call); +} + +DebugAppCheckProviderFactoryInternal::DebugAppCheckProviderFactoryInternal() + : provider_map_() {} + +DebugAppCheckProviderFactoryInternal::~DebugAppCheckProviderFactoryInternal() { + // Clear the map + for (auto it : provider_map_) { + delete it.second; + } + provider_map_.clear(); +} AppCheckProvider* DebugAppCheckProviderFactoryInternal::CreateProvider( App* app) { - return nullptr; + // Check the map + std::map::iterator it = provider_map_.find(app); + if (it != provider_map_.end()) { + return it->second; + } + // Create a new provider and cache it + AppCheckProvider* provider = new DebugAppCheckProvider(app); + provider_map_[app] = provider; + return provider; } } // namespace internal diff --git a/app_check/src/desktop/debug_provider_desktop.h b/app_check/src/desktop/debug_provider_desktop.h index a9519d0d62..ce8b84ca33 100644 --- a/app_check/src/desktop/debug_provider_desktop.h +++ b/app_check/src/desktop/debug_provider_desktop.h @@ -15,6 +15,8 @@ #ifndef FIREBASE_APP_CHECK_SRC_DESKTOP_DEBUG_PROVIDER_DESKTOP_H_ #define FIREBASE_APP_CHECK_SRC_DESKTOP_DEBUG_PROVIDER_DESKTOP_H_ +#include + #include "firebase/app_check.h" namespace firebase { @@ -28,6 +30,9 @@ class DebugAppCheckProviderFactoryInternal : public AppCheckProviderFactory { virtual ~DebugAppCheckProviderFactoryInternal(); AppCheckProvider* CreateProvider(App* app) override; + + private: + std::map provider_map_; }; } // namespace internal diff --git a/app_check/src/desktop/debug_token_request.fbs b/app_check/src/desktop/debug_token_request.fbs new file mode 100644 index 0000000000..65b2413757 --- /dev/null +++ b/app_check/src/desktop/debug_token_request.fbs @@ -0,0 +1,21 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace firebase.app_check.fbs; + +table DebugTokenRequest { + debug_token:string; +} + +root_type DebugTokenRequest; diff --git a/app_check/src/desktop/debug_token_request.h b/app_check/src/desktop/debug_token_request.h new file mode 100644 index 0000000000..ef891de0b4 --- /dev/null +++ b/app_check/src/desktop/debug_token_request.h @@ -0,0 +1,64 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FIREBASE_APP_CHECK_SRC_DESKTOP_DEBUG_TOKEN_REQUEST_H_ +#define FIREBASE_APP_CHECK_SRC_DESKTOP_DEBUG_TOKEN_REQUEST_H_ + +#include +#include + +#include "app/rest/request_json.h" +#include "app_check/debug_token_request_generated.h" +#include "app_check/debug_token_request_resource.h" +#include "firebase/app.h" + +namespace firebase { +namespace app_check { +namespace internal { + +// The server url to exchange the debug token with for a attestation token. +static const char* kDebugTokenRequestServerUrlBase = + "https://firebaseappcheck.googleapis.com/v1beta/projects/"; +// The header used to pass the project's API key. +static const char* kDebugTokenRequestHeader = "X-Goog-Api-Key"; + +// Request to exchange the user provided Debug Token with a valid attestation +// token. +class DebugTokenRequest + : public firebase::rest::RequestJson { + public: + explicit DebugTokenRequest(App* app) + : RequestJson(debug_token_request_resource_data) { + std::string server_url(kDebugTokenRequestServerUrlBase); + server_url.append(app->options().project_id()); + server_url.append("/apps/"); + server_url.append(app->options().app_id()); + server_url.append(":exchangeDebugToken"); + set_url(server_url.c_str()); + + add_header(kDebugTokenRequestHeader, app->options().api_key()); + } + + void SetDebugToken(std::string debug_token) { + application_data_->debug_token = std::move(debug_token); + UpdatePostFields(); + } +}; + +} // namespace internal +} // namespace app_check +} // namespace firebase + +#endif // FIREBASE_APP_CHECK_SRC_DESKTOP_DEBUG_TOKEN_REQUEST_H_ diff --git a/app_check/src/desktop/token_response.fbs b/app_check/src/desktop/token_response.fbs new file mode 100644 index 0000000000..4d49ab4c07 --- /dev/null +++ b/app_check/src/desktop/token_response.fbs @@ -0,0 +1,22 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace firebase.app_check.fbs; + +table TokenResponse { + token:string; + ttl:string; +} + +root_type TokenResponse; diff --git a/app_check/src/desktop/token_response.h b/app_check/src/desktop/token_response.h new file mode 100644 index 0000000000..de971d2e8f --- /dev/null +++ b/app_check/src/desktop/token_response.h @@ -0,0 +1,42 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FIREBASE_APP_CHECK_SRC_DESKTOP_TOKEN_RESPONSE_H_ +#define FIREBASE_APP_CHECK_SRC_DESKTOP_TOKEN_RESPONSE_H_ + +#include + +#include "app/rest/response_json.h" +#include "app_check/token_response_generated.h" +#include "app_check/token_response_resource.h" +#include "firebase/app.h" + +namespace firebase { +namespace app_check { +namespace internal { + +class TokenResponse : public firebase::rest::ResponseJson { + public: + TokenResponse() : ResponseJson(token_response_resource_data) {} + + const std::string& token() { return application_data_->token; } + const std::string& ttl() { return application_data_->ttl; } +}; + +} // namespace internal +} // namespace app_check +} // namespace firebase + +#endif // FIREBASE_APP_CHECK_SRC_DESKTOP_TOKEN_RESPONSE_H_