Merge remote-tracking branch 'origin/o365'

dependabot/npm_and_yarn/Src/WebController/UI/websocket-extensions-0.1.4
Pawel Kurowski 2020-05-15 12:29:49 +02:00
commit cbc4fb8969
20 changed files with 703 additions and 2 deletions

View File

@ -0,0 +1,77 @@
# Office365 Channels Readme
## Setup
Prior to using Office 365 Management APIs within C3, the steps below must be taken.
1. Register an app in Microsoft Azure.
2. Give the app the correct api permissions.
3. Add the app to allowed applications for desired user with Office365.
4. Pass authentication data to C3 channel.
#### Register app in Microsoft Azure
Outlook365RestTask and OneDrive365RestFile uses Office 365 Management API to send and receive data.
Each application requesting access to this API must be registered with specific API permissions.
Browse to:
```
https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredAppsPreview
```
Click "New registration". You should be presented with the following screen:
<img src="./images/office365/AzureRegister.JPG"/>
Add name for your application.
As supported account types choose "Accounts in any organizational directory and personal Microsoft accounts".
#### Give the app the correct api permissions
Directly after creation of the app, give it the correct permissions. To do this, click on the "API permissions" option.
You should be presented with the following screen:
<img src="./images/office365/AzureApiPermissions.JPG"/>
The following permissions are required:
* https://outlook.office365.com/Tasks.ReadWrite (Exchange/Delegated)
* https://graph.microsoft.com/Files.ReadWrite (Graph/Delegated)
#### Add the app to allowed applications for desired user with Office365
**Create separate account. Never use admin account transporting environment for C3!**
If user creating application is also an admin of target user tenant, it is possible to directly grant permission from "API permissions".
<img src="./images/office365/AzureGrantPermissionsAdmin.JPG"/>
Otherwise, target user must sign in to agree with usage of application popup.
<img src="./images/office365/AzureGrantPermissionsUser.JPG"/>
For onedrive channel use:
```
https://login.windows.net/common/oauth2/v2.0/authorize?client_id=<application_id>&scope=https://graph.microsoft.com/.default&response_type=code
```
For outlook channel use
```
https://login.windows.net/common/oauth2/v2.0/authorize?client_id=<application_id>&scope=https://outlook.office365.com/.default&response_type=code
```
#### Pass authentication data to C3 channel
With application data, and tartget username/password C3 can use Office 365 Management API.
<img src="./images/office365/CreatingChannel.jpg"/>
#### Add a Licence
As of May 2020 a business licence is required for the tenant user to have a mailbox. Without this the channel will not work. Login to admin.microsoft.com, purchase a "Microsoft 365 Business Standard" licence and assign it to the user who's credentials are to be used with the C3 channel.
## Rate Limit
Office Management API introduces rate limit for application. Each channel instance will send GET request every 3 to 6 seconds, to receive packets from server. Multiple channels accessing one office application can consume whole limit causing other connections to throttle.
Both Outlook365RestTask and OneDrive365RestFile are using delay stored in 429 error response header, to wait time period recommended by the server. Refreshed limit will allow some of the channels to perform correct transmission, but large amount of instances will consume new limit leaving other instances virtually unusable.
Avoid creating negotiation channels with the same credentials as in existing connections that could be expensive to lose

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -177,6 +177,28 @@ namespace FSecure::C3::Linter
throw std::exception("Data sent and received mismatch");
std::cout << "OK" << std::endl;
}
auto numberOfTests = 10;
auto packetSize = 64;
std::cout << "Testing channel order with " << numberOfTests << " packets of " << packetSize << " bytes of data ... " << std::flush;
std::vector<ByteVector> sent, received;
for (auto i = 0; i < numberOfTests; ++i)
{
sent.push_back(FSecure::Utils::GenerateRandomData(packetSize));
channel->GetDevice()->OnSendToChannelInternal(sent[i]);
}
for (auto i = 0; i < numberOfTests && received.size() < sent.size(); ++i)
{
auto receivedPackets = std::static_pointer_cast<C3::AbstractChannel>(complementary->GetDevice())->OnReceiveFromChannelInternal();
received.insert(received.end(), receivedPackets.begin(), receivedPackets.end());
std::this_thread::sleep_for(channel->GetDevice()->GetUpdateDelay());
}
if (sent != received)
throw std::exception("Data sent and received mismatch");
std::cout << "OK" << std::endl;
}
void ChannelLinter::TestCommand(std::shared_ptr<MockDeviceBridge> const& channel)

View File

@ -14,6 +14,8 @@
<ProjectCapability Include="SourceItemsFromImports" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\OneDrive365RestFile.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Outlook365RestTask.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Slack.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\UncShareFile.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\MSSQL.cpp" />
@ -28,6 +30,7 @@
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\CppTools\Compression.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\CppTools\Encryption.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\Crypto\Sodium.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\Crypto\String.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\Slack\SlackApi.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\Sockets\AddrInfo.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\Sockets\DuplexConnection.cpp" />
@ -50,6 +53,9 @@
<ClInclude Include="$(MSBuildThisFileDirectory)CppCodec\base32_default_crockford.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)CppCodec\base64_default_rfc4648.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)CppRestSdk\include\cpprest\http_client.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Office365.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\OneDrive365RestFile.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Outlook365RestTask.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\CppTools\ByteConverter\ByteArray.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\CppTools\ByteConverter\ByteConverter.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\CppTools\ByteConverter\ByteVector.h" />
@ -57,6 +63,7 @@
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\CppTools\ByteConverter\Utils.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\CppTools\StringConversions.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\Sql\Sql.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\Crypto\String.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\WinHttp\Config.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\WinHttp\Constants.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\WinHttp\HttpClient.h" />

View File

@ -25,6 +25,13 @@
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\WinTools\StructuredExceptionHandling.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\WinTools\InjectionBuffer.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\CppTools\Compression.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\OneDrive365RestFile.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Outlook365RestTask.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Connectors\MockServer.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Connectors\Covenant.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Peripherals\Mock.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Peripherals\Grunt.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\Crypto\String.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(MSBuildThisFileDirectory)CppCodec\base32_default_crockford.hpp" />
@ -91,6 +98,8 @@
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\CppTools\ByteConverter\ByteVector.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\CppTools\ByteConverter\ByteConverter.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\CppTools\ByteConverter\ByteArray.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\OneDrive365RestFile.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Outlook365RestTask.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\CppTools\StringConversions.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\Sql\Sql.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\WinTools\WindowsVersion.h" />
@ -100,5 +109,9 @@
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\WinHttp\HttpRequest.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\WinHttp\Uri.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\WinHttp\WebProxy.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Peripherals\Grunt.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Peripherals\Mock.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\Crypto\String.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Office365.h" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,229 @@
#pragma once
#include "Common/FSecure/WinHttp/HttpClient.h"
#include "Common/FSecure/WinHttp/HttpRequest.h"
#include "Common/FSecure/WinHttp/WebProxy.h"
#include "Common/FSecure/WinHttp/Constants.h"
#include "Common/FSecure/Crypto/String.h"
namespace FSecure::C3::Interfaces::Channels
{
/// Abstract of using Office365 API
template <typename Derived>
class Office365
{
public:
/// Public constructor.
/// @param arguments factory arguments.
Office365(ByteView arguments)
: m_InboundDirectionName{ arguments.Read<std::string>() }
, m_OutboundDirectionName{ arguments.Read<std::string>() }
, m_Username{ arguments.Read<SecureString>() }
, m_Password{ arguments.Read<SecureString>() }
, m_ClientKey{ arguments.Read<SecureString>() }
{
FSecure::Utils::DisallowChars({ m_InboundDirectionName, m_OutboundDirectionName }, OBF(R"(;/?:@&=+$,)"));
// Obtain proxy information and store it in the HTTP configuration.
if (auto winProxy = WinTools::GetProxyConfiguration(); !winProxy.empty())
this->m_ProxyConfig = (winProxy == OBF(L"auto")) ? WebProxy(WebProxy::Mode::UseAutoDiscovery) : WebProxy(winProxy);
RefreshAccessToken();
}
/// Get channel capability.
/// @returns ByteView view of channel capability.
static ByteView GetCapability();
protected:
/// Remove one item from server.
/// @param id of task.
void RemoveItem(std::string const& id)
{
auto webClient = HttpClient{ Convert<Utf16>(Derived::ItemEndpoint.Decrypt() + SecureString{id}), m_ProxyConfig };
auto request = CreateAuthRequest(Method::DEL);
auto resp = webClient.Request(request);
if (resp.GetStatusCode() > 205)
throw std::runtime_error{ OBF("RemoveItem() Error. Task ") + id + OBF(" could not be deleted. HTTP response:") + std::to_string(resp.GetStatusCode()) };
}
/// Removes all items from server.
void RemoveAllItems()
{
auto fileList = ListData();
for (auto& element : fileList.at(OBF("value")))
RemoveItem(element.at(OBF("id")).get<std::string>());
}
/// Requests a new access token using the refresh token
/// @throws std::exception if token cannot be refreshed.
void RefreshAccessToken()
{
try
{
//Token endpoint
auto webClient = HttpClient{ Convert<Utf16>(Derived::TokenEndpoint.Decrypt()), m_ProxyConfig };
auto request = HttpRequest{ Method::POST };
auto requestBody = SecureString{};
requestBody += OBF("grant_type=password");
requestBody += OBF("&scope=");
requestBody += Derived::Scope.Decrypt();
requestBody += OBF("&username=");
requestBody += m_Username.Decrypt();
requestBody += OBF("&password=");
requestBody += m_Password.Decrypt();
requestBody += OBF("&client_id=");
requestBody += m_ClientKey.Decrypt();
request.SetData(ContentType::ApplicationXWwwFormUrlencoded, { requestBody.begin(), requestBody.end() });
auto resp = webClient.Request(request);
EvaluateResponse(resp, false);
auto data = json::parse(resp.GetData());
m_Token = data[OBF("access_token")].get<std::string>();
}
catch (std::exception & exception)
{
throw std::runtime_error{ OBF_STR("Cannot refresh token: ") + exception.what() };
}
}
/// Check if request was successful.
/// @throws std::exception describing incorrect response if occurred.
void EvaluateResponse(WinHttp::HttpResponse const& resp, bool tryRefreshingToken = true)
{
if (resp.GetStatusCode() == StatusCode::OK || resp.GetStatusCode() == StatusCode::Created)
return;
if (resp.GetStatusCode() == StatusCode::TooManyRequests) // break and set sleep time.
{
auto retryAfterHeader = resp.GetHeader(Header::RetryAfter);
auto delay = !retryAfterHeader.empty() ? stoul(retryAfterHeader) : FSecure::Utils::GenerateRandomValue(10, 20);
s_TimePoint = std::chrono::steady_clock::now() + std::chrono::seconds{ delay };
throw std::runtime_error{ OBF("Too many requests") };
}
if (resp.GetStatusCode() == StatusCode::Unauthorized)
{
if (tryRefreshingToken)
RefreshAccessToken();
throw std::runtime_error{ OBF("HTTP 401 - Token being refreshed") };
}
if (resp.GetStatusCode() == StatusCode::BadRequest)
throw std::runtime_error{ OBF("Bad Request") };
throw std::runtime_error{ OBF("Non 200 http response.") + std::to_string(resp.GetStatusCode()) };
}
/// Create request using internally stored token.
/// @param method, request type.
WinHttp::HttpRequest CreateAuthRequest(WinHttp::Method method = WinHttp::Method::GET)
{
auto request = HttpRequest{ method };
request.SetHeader(Header::Authorization, Convert<Utf16>(OBF_SEC("Bearer ") + m_Token.Decrypt()));
return request;
}
/// Server will reject request send before s_TimePoint. This method will await to this point plus extra random delay.
/// @param min lower limit of random delay.
/// @param max upper limit of random delay.
void RateLimitDelay(std::chrono::milliseconds min, std::chrono::milliseconds max)
{
if (s_TimePoint.load() > std::chrono::steady_clock::now())
std::this_thread::sleep_until(s_TimePoint.load() + FSecure::Utils::GenerateRandomValue(min, max));
}
/// List files on server.
/// @param filter flags to filter data from server.
/// @return json server response.
json ListData(std::string_view filter = {})
{
auto webClient = HttpClient{ Convert<Utf16>(Derived::ListEndpoint.Decrypt() + SecureString{ filter }), m_ProxyConfig };
auto request = CreateAuthRequest();
auto resp = webClient.Request(request);
EvaluateResponse(resp);
return json::parse(resp.GetData());
}
/// In/Out names on the server.
std::string m_InboundDirectionName, m_OutboundDirectionName;
/// Username, password, client key and token for authentication.
Crypto::String m_Username, m_Password, m_ClientKey, m_Token;
/// Store any relevant proxy info
WinHttp::WebProxy m_ProxyConfig;
/// Used to delay every channel instance in case of server rate limit.
/// Set using information from 429 Too Many Requests header.
static std::atomic<std::chrono::steady_clock::time_point> s_TimePoint;
};
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename Derived>
std::atomic<std::chrono::steady_clock::time_point> FSecure::C3::Interfaces::Channels::Office365<Derived>::s_TimePoint = std::chrono::steady_clock::now();
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename Derived>
FSecure::ByteView FSecure::C3::Interfaces::Channels::Office365<Derived>::GetCapability()
{
return R"_(
{
"create":
{
"arguments":
[
[
{
"type": "string",
"name": "Input ID",
"min": 4,
"randomize": true,
"description": "Used to distinguish packets for the channel"
},
{
"type": "string",
"name": "Output ID",
"min": 4,
"randomize": true,
"description": "Used to distinguish packets from the channel"
}
],
{
"type": "string",
"name": "username",
"min": 1,
"description": "The O365 user"
},
{
"type": "string",
"name": "password",
"min": 1,
"description": "The user's password"
},
{
"type": "string",
"name": "Client Key/ID",
"min": 1,
"description": "The GUID of the registered application."
}
]
},
"commands":
[
{
"name": "Clear channel",
"id": 0,
"description": "Clearing old files from server. May increase bandwidth",
"arguments": []
}
]
}
)_";
}

View File

@ -0,0 +1,118 @@
#include "StdAfx.h"
#include "OneDrive365RestFile.h"
#include "Common/FSecure/Crypto/Base64.h"
#include "Common/FSecure/CppTools/ScopeGuard.h"
#include "Common/json/json.hpp"
#include "Common/FSecure/CppTools/StringConversions.h"
#include "Common/FSecure/WinHttp/HttpClient.h"
#include "Common/FSecure/WinHttp/Constants.h"
#include "Common/FSecure/WinHttp/Uri.h"
// Namespaces.
using json = nlohmann::json;
using base64 = cppcodec::base64_rfc4648;
using namespace FSecure::StringConversions;
using namespace FSecure::WinHttp;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::OneDrive365RestFile::RootEndpoint = OBF("https://graph.microsoft.com/v1.0/me/drive/root:/");
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::OneDrive365RestFile::ItemEndpoint = OBF("https://graph.microsoft.com/v1.0/me/drive/items/");
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::OneDrive365RestFile::ListEndpoint = OBF("https://graph.microsoft.com/v1.0/me/drive/root/children");
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::OneDrive365RestFile::TokenEndpoint = OBF("https://login.windows.net/organizations/oauth2/v2.0/token");
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::OneDrive365RestFile::Scope = OBF("files.readwrite.all");
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
size_t FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnSendToChannel(ByteView data)
{
RateLimitDelay(m_MinUpdateDelay, m_MaxUpdateDelay);
try
{
// Construct the HTTP request
auto URLwithFilename = RootEndpoint.Decrypt().c_str() + m_OutboundDirectionName + OBF("-") + FSecure::Utils::GenerateRandomString(20) + OBF(".json") + OBF(":/content");
auto webClient = HttpClient{ Convert<Utf16>(URLwithFilename), m_ProxyConfig };
auto request = CreateAuthRequest(Method::PUT);
auto chunkSize = std::min<size_t>(data.size(), base64::decoded_max_size(4 * 1024 * 1024 - 256)); // Send max 4 MB. 256 bytes are reserved for json schema.
auto fileData = json{};
fileData[OBF("epoch_time")] = FSecure::Utils::TimeSinceEpoch();
fileData[OBF("high_res_time")] = GetTickCount64();
fileData[OBF("data")] = base64::encode(&data.front(), chunkSize);
auto body = fileData.dump();
request.SetData(ContentType::TextPlain, { body.begin(), body.end() });
EvaluateResponse(webClient.Request(request));
return chunkSize;
}
catch (std::exception& exception)
{
Log({ OBF_SEC("Caught a std::exception when running OnSend(): ") + exception.what(), LogMessage::Severity::Error });
return 0u;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
std::vector<FSecure::ByteVector> FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnReceiveFromChannel()
{
RateLimitDelay(m_MinUpdateDelay, m_MaxUpdateDelay);
auto packets = std::vector<ByteVector>{};
try
{
auto fileList = ListData(OBF("?top=1000&filter=startswith(name,'") + m_InboundDirectionName + OBF("')"));
// First iterate over the json and populate an array of the files we want.
auto elements = std::vector<json>{};
for (auto& element : fileList.at(OBF("value")))
{
//download the file
auto webClientFile = HttpClient{ Convert<Utf16>(element.at(OBF("@microsoft.graph.downloadUrl")).get<std::string>()), m_ProxyConfig };
auto request = CreateAuthRequest();
auto resp = webClientFile.Request(request);
EvaluateResponse(resp);
auto j = json::parse(resp.GetData());
j[OBF("id")] = element.at(OBF("id"));
elements.push_back(std::move(j));
}
//now sort and re-iterate over them.
std::sort(elements.begin(), elements.end(),
[](auto const& a, auto const& b) { return a[OBF("epoch_time")] < b[OBF("epoch_time")] || a[OBF("high_res_time")] < b[OBF("high_res_time")]; }
);
for(auto &element : elements)
packets.push_back(base64::decode<ByteVector, std::string>(element.at(OBF("data"))));
for (auto& element : elements)
RemoveItem(element.at(OBF("id")));
}
catch (std::exception& exception)
{
Log({ OBF_SEC("Caught a std::exception when running OnReceive(): ") + exception.what(), LogMessage::Severity::Warning });
}
return packets;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::ByteVector FSecure::C3::Interfaces::Channels::OneDrive365RestFile::OnRunCommand(ByteView command)
{
auto commandCopy = command; // Each read moves ByteView. CommandCoppy is needed for default.
switch (command.Read<uint16_t>())
{
case 0:
try
{
RemoveAllItems();
}
catch (std::exception const& e)
{
Log({ OBF_SEC("Caught a std::exception when running RemoveAllFiles(): ") + e.what(), LogMessage::Severity::Error });
}
return {};
default:
return AbstractChannel::OnRunCommand(commandCopy);
}
}

View File

@ -0,0 +1,40 @@
#pragma once
#include "Office365.h"
namespace FSecure::C3::Interfaces::Channels
{
class OneDrive365RestFile : public Channel<OneDrive365RestFile>, public Office365<OneDrive365RestFile>
{
public:
/// Use Office365 constructor.
using Office365<OneDrive365RestFile>::Office365;
/// OnSend callback implementation.
/// @param blob data to send to Channel.
/// @returns size_t number of bytes successfully written.
size_t OnSendToChannel(ByteView blob);
/// Reads a single C3 packet from Channel.
/// @return packet retrieved from Channel.
std::vector<ByteVector> OnReceiveFromChannel();
/// Processes internal (C3 API) Command.
/// @param command a buffer containing whole command and it's parameters.
/// @return command result.
ByteVector OnRunCommand(ByteView command) override;
/// Values used as default for channel jitter. 30 ms if unset. Current jitter value can be changed at runtime.
/// Set long delay otherwise O365 rate limit will heavily impact channel.
constexpr static std::chrono::milliseconds s_MinUpdateDelay = 1000ms, s_MaxUpdateDelay = 1000ms;
/// Endpoint used to add file to OneDrive
static Crypto::String RootEndpoint;
/// Endpoints used by Office365 methods.
static Crypto::String ItemEndpoint;
static Crypto::String ListEndpoint;
static Crypto::String TokenEndpoint;
static Crypto::String Scope;
};
}

View File

@ -0,0 +1,96 @@
#include "StdAfx.h"
#include "Outlook365RestTask.h"
#include "Common/FSecure/Crypto/Base64.h"
#include "Common/FSecure/CppTools/ScopeGuard.h"
#include "Common/json/json.hpp"
#include "Common/FSecure/CppTools/StringConversions.h"
#include "Common/FSecure/WinHttp/HttpClient.h"
#include "Common/FSecure/WinHttp/Constants.h"
#include "Common/FSecure/WinHttp/Uri.h"
// Namespaces
using json = nlohmann::json;
using base64 = cppcodec::base64_rfc4648;
using namespace FSecure::StringConversions;
using namespace FSecure::WinHttp;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::Outlook365RestTask::ItemEndpoint = OBF("https://outlook.office.com/api/v2.0/me/tasks/");
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::Outlook365RestTask::ListEndpoint = OBF("https://outlook.office.com/api/v2.0/me/tasks");
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::Outlook365RestTask::TokenEndpoint = OBF("https://login.windows.net/organizations/oauth2/v2.0/token/");
FSecure::Crypto::String FSecure::C3::Interfaces::Channels::Outlook365RestTask::Scope = OBF("https://outlook.office365.com/.default");
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
size_t FSecure::C3::Interfaces::Channels::Outlook365RestTask::OnSendToChannel(ByteView data)
{
RateLimitDelay(m_MinUpdateDelay, m_MaxUpdateDelay);
try
{
// Construct the HTTP request
auto webClient = HttpClient{ Convert<Utf16>(ItemEndpoint.Decrypt()), m_ProxyConfig };
auto request = CreateAuthRequest(Method::POST);
auto chunkSize = std::min<size_t>(data.size(), base64::decoded_max_size(4 * 1024 * 1024) ); // Send max 4 MB.
auto fileData = json();
fileData[OBF("Subject")] = m_OutboundDirectionName;
fileData[OBF("Body")][OBF("Content")] = base64::encode(&data.front(), chunkSize);
fileData[OBF("Body")][OBF("ContentType")] = OBF("Text");
auto body = fileData.dump();
request.SetData(ContentType::ApplicationJson, { body.begin(), body.end() });
EvaluateResponse(webClient.Request(request));
return chunkSize;
}
catch (std::exception & exception)
{
Log({ OBF_SEC("Caught a std::exception when running OnSend(): ") + exception.what(), LogMessage::Severity::Error });
return 0u;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
std::vector<FSecure::ByteVector> FSecure::C3::Interfaces::Channels::Outlook365RestTask::OnReceiveFromChannel()
{
RateLimitDelay(m_MinUpdateDelay, m_MaxUpdateDelay);
auto packets = std::vector<ByteVector>{};
try
{
auto fileList = ListData(OBF("?top=1000&filter=startswith(Subject,'") + m_InboundDirectionName + OBF("')&orderby=CreatedDateTime"));
for (auto& element : fileList.at(OBF("value")))
packets.push_back(base64::decode<ByteVector, std::string>(element.at(OBF("Body")).at(OBF("Content"))));
for (auto& element : fileList.at(OBF("value")))
RemoveItem(element.at(OBF("Id")));
}
catch (std::exception& exception)
{
Log({ OBF_SEC("Caught a std::exception when running OnReceive(): ") + exception.what(), LogMessage::Severity::Warning });
}
return packets;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FSecure::ByteVector FSecure::C3::Interfaces::Channels::Outlook365RestTask::OnRunCommand(ByteView command)
{
auto commandCopy = command; // Each read moves ByteView. CommandCoppy is needed for default.
switch (command.Read<uint16_t>())
{
case 0:
try
{
RemoveAllItems();
}
catch (std::exception const& e)
{
Log({ OBF_SEC("Caught a std::exception when running RemoveAllFiles(): ") + e.what(), LogMessage::Severity::Error });
}
return {};
default:
return AbstractChannel::OnRunCommand(commandCopy);
}
}

View File

@ -0,0 +1,39 @@
#pragma once
#include "Office365.h"
namespace FSecure::C3::Interfaces::Channels
{
/// Implementation of the Outlook365 REST tasks Device.
class Outlook365RestTask : public Channel<Outlook365RestTask>, public Office365<Outlook365RestTask>
{
public:
/// Use Office365 constructor.
using Office365<Outlook365RestTask>::Office365;
/// OnSend callback implementation.
/// @param blob data to send to Channel.
/// @returns size_t number of bytes successfully written.
size_t OnSendToChannel(ByteView blob);
/// Reads a single C3 packet from Channel.
/// @return packets retrieved from Channel.
std::vector<FSecure::ByteVector> OnReceiveFromChannel();
/// Processes internal (C3 API) Command.
/// @param command a buffer containing whole command and it's parameters.
/// @return command result.
ByteVector OnRunCommand(ByteView command) override;
/// Values used as default for channel jitter. 30 ms if unset. Current jitter value can be changed at runtime.
/// Set long delay otherwise O365 rate limit will heavily impact channel.
constexpr static std::chrono::milliseconds s_MinUpdateDelay = 1000ms, s_MaxUpdateDelay = 1000ms;
/// Endpoints used by Office365 methods.
static Crypto::String ItemEndpoint;
static Crypto::String ListEndpoint;
static Crypto::String TokenEndpoint;
static Crypto::String Scope;
};
}

View File

@ -34,7 +34,7 @@ namespace FSecure::StringConversions
{
/// Detect character type stored by container.
template <typename T>
using CharT = std::remove_reference_t<decltype(std::declval<T>()[0])>;
using CharT = std::remove_const_t<std::remove_reference_t<decltype(std::declval<T>()[0])>>;
/// Detect view type corresponding to container.
template <typename T>

View File

@ -235,4 +235,18 @@ namespace FSecure::Utils
return retValue;
}
/// Throw runtime_error if any of forbidden character is present in the string.
inline void DisallowChars(std::string_view word, std::string_view forbiddenChars)
{
if (auto pos = word.find_first_of(forbiddenChars); pos != std::string::npos)
throw std::runtime_error{ OBF_STR("Forbidden character found: ") + word[pos] };
}
/// Throw runtime_error if any of forbidden character is present in any of the string.
inline void DisallowChars(std::vector<std::string_view> words, std::string_view forbiddenChars)
{
for (auto word : words)
DisallowChars(word, forbiddenChars);
}
}

View File

@ -0,0 +1,23 @@
#include "StdAfx.h"
#include "Common/FSecure/Crypto/String.h"
FSecure::Crypto::String::String(std::string_view sv)
: m_Key{ GenerateSymmetricKey() }
, m_Data{ EncryptAnonymously(sv, m_Key) }
{
}
FSecure::SecureString FSecure::Crypto::String::Decrypt()
{
auto tmp = m_Data.empty() ? ByteVector{} : DecryptFromAnonymous(m_Data, m_Key);
return { tmp.begin(), tmp.end() };
}
FSecure::Crypto::String& FSecure::Crypto::String::operator=(std::string_view sv)
{
m_Key = GenerateSymmetricKey();
m_Data = EncryptAnonymously(sv, m_Key);
return *this;
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <string_view>
#include "Common/FSecure/SecureString.hpp"
#include "Common/FSecure/CppTools/ByteConverter/ByteView.h"
#include "Common/FSecure/Crypto/Crypto.hpp"
namespace FSecure::Crypto
{
class String
{
public:
String() = default;
String(std::string_view sv);
String& operator=(std::string_view sv);
SecureString Decrypt();
private:
SymmetricKey m_Key; // This is poor man implementation, key should be kept in key storage.
ByteVector m_Data;
};
}

View File

@ -32,7 +32,7 @@ namespace FSecure::WinHttp
/// @param header - one of known HTTP headers to retrieve
/// @returns HTTP header value
/// @throws std::runtime_error if header value cannot be retreived
std::wstring GetHeader(Header header)
std::wstring GetHeader(Header header) const
{
return GetHeader(GetHeaderName(header));
}