From 46cba2826a71de18c3ecec88a1ae8d6139ba77fc Mon Sep 17 00:00:00 2001 From: Sunny Wu Date: Wed, 21 Aug 2024 22:02:27 +1000 Subject: [PATCH] Syw UID2-3679 cpp sdk base64 v4 token (#26) * added new CLion<=>Docker<=>Mac OS dev instructions * able to decrypt Base64 (non url friendly) encoded v4 ad tokens * changed to release version 3.1.0 --- CMakeLists.txt | 2 +- README.md | 9 +++++++ lib/uid2encryption.cpp | 14 +++++++---- test/encryption_tests_v4.cpp | 42 ++++++++++++++++++++++++++------- tools/Dockerfile.cpp-env-ubuntu | 28 ++++++++++++++++++++++ 5 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 tools/Dockerfile.cpp-env-ubuntu diff --git a/CMakeLists.txt b/CMakeLists.txt index 08d39d3..ed623a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.12) -project("uid2-client" VERSION 3.0.1) +project("uid2-client" VERSION 3.1.0) configure_file(VERSION.in VERSION @ONLY) diff --git a/README.md b/README.md index e53e610..6eb4ebd 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,15 @@ sudo ln -s $(brew --prefix llvm@14)/bin/clang-format /usr/local/bin/clang-format sudo ln -s $(brew --prefix llvm@14)/bin/clang-tidy /usr/local/bin/clang-tidy-14 ``` +## Build and Test in CLion Using Docker on Mac OS +Run +``` +docker build -t clion/ubuntu/cpp-env:1.0 -f tools/Dockerfile.cpp-env-ubuntu . +``` +And [setup a Docker Toolchain in CLion](https://www.jetbrains.com/help/clion/clion-toolchains-in-docker.html) + +And you would be able to develop and test within CLion easily. + ## Build, Test, Install To build, run unit tests, and install under the default prefix (`/usr/local`): diff --git a/lib/uid2encryption.cpp b/lib/uid2encryption.cpp index 98dac6a..b7c8810 100644 --- a/lib/uid2encryption.cpp +++ b/lib/uid2encryption.cpp @@ -44,11 +44,12 @@ DecryptionResult DecryptToken(const std::string& token, const KeyContainer& keys return DecryptionResult::MakeError(DecryptionStatus::INVALID_PAYLOAD); } - const std::string headerStr = token.substr(0, 4); - const bool isBase64UrlEncoding = std::any_of(headerStr.begin(), headerStr.end(), [](char c) { return c == '-' || c == '_'; }); + // check the whole ad token string instead of the headerStr to make sure + const bool isBase64UrlEncoding = std::any_of(token.begin(), token.end(), [](char c) { return c == '-' || c == '_'; }); try { std::vector encryptedId; std::vector headerBytes; + const std::string headerStr = token.substr(0, 4); if (isBase64UrlEncoding) { uid2::UID2Base64UrlCoder::Decode(headerStr, headerBytes); @@ -69,8 +70,13 @@ DecryptionResult DecryptToken(const std::string& token, const KeyContainer& keys return DecryptTokenV3(encryptedId, keys, now, identityScope, checkValidity); } if (headerBytes[1] == static_cast(AdvertisingTokenVersion::V4)) { - // same as V3 but use Base64URL encoding - uid2::UID2Base64UrlCoder::Decode(token, encryptedId); + if (isBase64UrlEncoding) { + // same as V3 but use Base64URL encoding + uid2::UID2Base64UrlCoder::Decode(token, encryptedId); + } else { + // handling the rare situation where participant changed the encoding from Base64URL to Base64 + macaron::Base64::Decode(token, encryptedId); + } return DecryptTokenV3(encryptedId, keys, now, identityScope, checkValidity); } return DecryptionResult::MakeError(DecryptionStatus::INVALID_PAYLOAD); diff --git a/test/encryption_tests_v4.cpp b/test/encryption_tests_v4.cpp index 4463280..0b60b14 100644 --- a/test/encryption_tests_v4.cpp +++ b/test/encryption_tests_v4.cpp @@ -8,6 +8,7 @@ #include +#include #include using namespace uid2; @@ -116,15 +117,43 @@ std::string GenerateUid2TokenV4AndValidate( return advertisingToken; } +void DecryptAndAssertSuccess(UID2Client& client, const std::string& advertisingTokenString, Timestamp timestamp = Timestamp::Now()) +{ + const auto res = client.Decrypt(advertisingTokenString, timestamp); + EXPECT_TRUE(res.IsSuccess()); + EXPECT_EQ(DecryptionStatus::SUCCESS, res.GetStatus()); + EXPECT_EQ(EXAMPLE_UID, res.GetUid()); +} + +TEST(EncryptionTestsV4, CanDecryptV4TokenEncodedAsBase64) +{ + UID2Client client("ep", "ak", CLIENT_SECRET, IdentityScope::UID2); + client.RefreshJson(KeySetToJson({MASTER_KEY, SITE_KEY})); + std::string advertisingToken; + + // for testing purposes, the token must have some Base64URL encoding characters + do { + advertisingToken = GenerateUid2TokenV4AndValidate(EXAMPLE_UID, MASTER_KEY, SITE_ID, SITE_KEY, EncryptTokenParams()); + } while (!std::any_of(advertisingToken.begin(), advertisingToken.end(), [](char c) { return c == '-' || c == '_'; })); + + std::vector adTokenBytes; + uid2::UID2Base64UrlCoder::Decode(advertisingToken, adTokenBytes); + + // explicitly encode into Base64 (non-URL friendly) encoding again + const auto base64NonURLAdTokenV4 = macaron::Base64::Encode(adTokenBytes); + const bool isBase64NonUrlEncoding = + std::any_of(base64NonURLAdTokenV4.begin(), base64NonURLAdTokenV4.end(), [](char c) { return c == '=' || c == '+' || c == '/'; }); + EXPECT_TRUE(isBase64NonUrlEncoding); + + DecryptAndAssertSuccess(client, base64NonURLAdTokenV4); +} + TEST(EncryptionTestsV4, SmokeTest) { UID2Client client("ep", "ak", CLIENT_SECRET, IdentityScope::UID2); client.RefreshJson(KeySetToJson({MASTER_KEY, SITE_KEY})); const auto advertisingToken = GenerateUid2TokenV4AndValidate(EXAMPLE_UID, MASTER_KEY, SITE_ID, SITE_KEY, EncryptTokenParams()); - const auto res = client.Decrypt(advertisingToken, Timestamp::Now()); - EXPECT_TRUE(res.IsSuccess()); - EXPECT_EQ(DecryptionStatus::SUCCESS, res.GetStatus()); - EXPECT_EQ(EXAMPLE_UID, res.GetUid()); + DecryptAndAssertSuccess(client, advertisingToken); } TEST(EncryptionTestsV4, EmptyKeyContainer) @@ -188,10 +217,7 @@ TEST(EncryptionTestsV4, TokenExpiryAndCustomNow) EXPECT_FALSE(res.IsSuccess()); EXPECT_EQ(DecryptionStatus::EXPIRED_TOKEN, res.GetStatus()); - res = client.Decrypt(advertisingToken, expiry.AddSeconds(-1)); - EXPECT_TRUE(res.IsSuccess()); - EXPECT_EQ(DecryptionStatus::SUCCESS, res.GetStatus()); - EXPECT_EQ(EXAMPLE_UID, res.GetUid()); + DecryptAndAssertSuccess(client, advertisingToken, expiry.AddSeconds(-1)); } TEST(EncryptDataTestsV4, SiteIdFromToken) diff --git a/tools/Dockerfile.cpp-env-ubuntu b/tools/Dockerfile.cpp-env-ubuntu new file mode 100644 index 0000000..251b1a0 --- /dev/null +++ b/tools/Dockerfile.cpp-env-ubuntu @@ -0,0 +1,28 @@ +# Based on https://github.com/JetBrains/clion-remote/blob/master/Dockerfile.cpp-env-ubuntu +# Build and run: +# docker build -t clion/ubuntu/cpp-env:1.0 -f Dockerfile.cpp-env-ubuntu . + +FROM ubuntu:22.04 + +RUN DEBIAN_FRONTEND="noninteractive" apt-get update && apt-get -y install tzdata + +RUN apt-get update \ + && apt-get install -y build-essential \ + gcc \ + g++ \ + gdb \ + clang \ + make \ + ninja-build \ + cmake \ + autoconf \ + automake \ + libtool \ + valgrind \ + locales-all \ + dos2unix \ + rsync \ + tar \ + libssl-dev \ + libgtest-dev \ + && apt-get clean