Skip to content

Add desktop Debug provider for App Check #1126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions app_check/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,50 @@ 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
src/desktop/app_check_desktop.h
# 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
Expand Down
105 changes: 102 additions & 3 deletions app_check/src/desktop/debug_provider_desktop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,118 @@

#include "app_check/src/desktop/debug_provider_desktop.h"

#include <map>
#include <string>
#include <utility>

#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<void(AppCheckToken, int, const std::string&)>
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<DebugTokenRequest> request,
std::function<void(AppCheckToken, int, const std::string&)>
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<void(AppCheckToken, int, const std::string&)>
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<DebugTokenRequest>(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<App*, AppCheckProvider*>::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
Expand Down
5 changes: 5 additions & 0 deletions app_check/src/desktop/debug_provider_desktop.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <map>

#include "firebase/app_check.h"

namespace firebase {
Expand All @@ -28,6 +30,9 @@ class DebugAppCheckProviderFactoryInternal : public AppCheckProviderFactory {
virtual ~DebugAppCheckProviderFactoryInternal();

AppCheckProvider* CreateProvider(App* app) override;

private:
std::map<App*, AppCheckProvider*> provider_map_;
};

} // namespace internal
Expand Down
21 changes: 21 additions & 0 deletions app_check/src/desktop/debug_token_request.fbs
Original file line number Diff line number Diff line change
@@ -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;
64 changes: 64 additions & 0 deletions app_check/src/desktop/debug_token_request.h
Original file line number Diff line number Diff line change
@@ -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 <string>
#include <utility>

#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<fbs::DebugTokenRequest,
fbs::DebugTokenRequestT> {
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_
22 changes: 22 additions & 0 deletions app_check/src/desktop/token_response.fbs
Original file line number Diff line number Diff line change
@@ -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;
42 changes: 42 additions & 0 deletions app_check/src/desktop/token_response.h
Original file line number Diff line number Diff line change
@@ -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 <string>

#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<fbs::TokenResponse,
fbs::TokenResponseT> {
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_