Skip to content

Commit

Permalink
feat(E2EE-2407): Add support of PrehashedAndEncryptedPassphrase in En…
Browse files Browse the repository at this point in the history
…roll API
  • Loading branch information
ntalfer committed Dec 19, 2024
1 parent e2fc208 commit e47b662
Show file tree
Hide file tree
Showing 16 changed files with 705 additions and 354 deletions.
66 changes: 63 additions & 3 deletions modules/functional-tests/test_verification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ OidcIdToken alterOidcTokenSignature(OidcIdToken const& idToken)
*itSig = b64::encode(alterSig);
return OidcIdToken{ba::join(res, "."), {}, {}};
}

Verification::Verification createPAEPVerification(std::string password)
{
auto const passphrasePublicEncryptionKey = TestConstants::passphrasePublicEncryptionKey();
std::string paep = mgs::base64::encode(Crypto::prehashAndEncryptPassword(password, passphrasePublicEncryptionKey));
return Verification::Verification{PrehashedAndEncryptedPassphrase{paep}};
}
}

TEST_CASE_METHOD(TrustchainFixture, "Verification")
Expand Down Expand Up @@ -1153,6 +1160,50 @@ TEST_CASE_METHOD(TrustchainFixture, "Verification with preverified oidc")
}
}

TEST_CASE_METHOD(TrustchainFixture, "Verification with prehashed and encrypted passphrase")
{
auto gerard = trustchain.makeUser();
auto device1 = gerard.makeDevice();
auto laptop = device1.createCore();
auto device2 = gerard.makeDevice();
auto phone = device2.createCore();

REQUIRE(TC_AWAIT(laptop->start(gerard.identity)) == Status::IdentityRegistrationNeeded);

auto const email = makeEmail();
auto const prehashedAndEncryptedPassphraseVerification = createPAEPVerification("gErtruD");

SECTION("registerIdentity throws when verification method is prehashed and encrypted passphrase")
{
TANKER_CHECK_THROWS_WITH_CODE(TC_AWAIT(laptop->registerIdentity(prehashedAndEncryptedPassphraseVerification)),
Errc::InvalidArgument);
REQUIRE(laptop->status() == Status::IdentityRegistrationNeeded);
}

SECTION("verifyIdentity throws when verification method is prehashed and encrypted passphrase")
{
auto verificationCode = TC_AWAIT(getVerificationCode(email));
REQUIRE_NOTHROW(TC_AWAIT(
laptop->registerIdentity(Verification::Verification{Verification::ByEmail{email, verificationCode}})));

CHECK_NOTHROW(checkVerificationMethods(TC_AWAIT(laptop->getVerificationMethods()), {email}));

REQUIRE(TC_AWAIT(phone->start(gerard.identity)) == Status::IdentityVerificationNeeded);

TANKER_CHECK_THROWS_WITH_CODE(TC_AWAIT(phone->verifyIdentity(prehashedAndEncryptedPassphraseVerification)),
Errc::InvalidArgument);

REQUIRE(phone->status() == Status::IdentityVerificationNeeded);
}

SECTION("setVerificationMethod throws when verification method is prehashed and encrypted passphrase")
{
REQUIRE_NOTHROW(TC_AWAIT(laptop->registerIdentity(Passphrase{"******"})));
TANKER_CHECK_THROWS_WITH_CODE(TC_AWAIT(laptop->setVerificationMethod(prehashedAndEncryptedPassphraseVerification)),
Errc::InvalidArgument);
}
}

TEST_CASE_METHOD(TrustchainFixture, "User enrollment errors")
{
TC_AWAIT(enableOidc());
Expand All @@ -1170,6 +1221,7 @@ TEST_CASE_METHOD(TrustchainFixture, "User enrollment errors")
auto const providerId = oidcProviderId(server->sdkInfo().trustchainId, oidcConfig.issuer, oidcConfig.clientId);
auto const subject = getOidcSubject(martineIdToken);
auto const oidcVerification = PreverifiedOidc{providerId, subject};
auto const prehashedAndEncryptedPassphraseVerification = createPAEPVerification("Test:1-2-1-2");

auto enrolledUser = trustchain.makeUser();

Expand Down Expand Up @@ -1215,6 +1267,7 @@ TEST_CASE_METHOD(TrustchainFixture, "User enrollment errors")
{emailVerification, emailVerification},
{phoneNumberVerification, phoneNumberVerification},
{oidcVerification, oidcVerification},
{prehashedAndEncryptedPassphraseVerification, prehashedAndEncryptedPassphraseVerification}
};

for (auto const& verifications : badPreverifiedVerifications)
Expand Down Expand Up @@ -1267,6 +1320,8 @@ TEST_CASE_METHOD(TrustchainFixture, "User enrollment")
auto const subject = getOidcSubject(martineIdToken);
auto const oidcVerification = PreverifiedOidc{providerId, subject};

auto const clearPassphrase = "Prenez1ChewingGumEmile!";
auto const prehashedAndEncryptedPassphraseVerification = createPAEPVerification(clearPassphrase);
auto enrolledUser = trustchain.makeUser();

SECTION("server")
Expand All @@ -1289,7 +1344,7 @@ TEST_CASE_METHOD(TrustchainFixture, "User enrollment")
SECTION("enrolls a user with every preverified verification method")
{
REQUIRE_NOTHROW(TC_AWAIT(
server->enrollUser(enrolledUser.identity, {emailVerification, phoneNumberVerification, oidcVerification})));
server->enrollUser(enrolledUser.identity, {emailVerification, phoneNumberVerification, oidcVerification, prehashedAndEncryptedPassphraseVerification})));
}

SECTION("stays STOPPED after enrolling a user")
Expand All @@ -1306,7 +1361,7 @@ TEST_CASE_METHOD(TrustchainFixture, "User enrollment")
auto device1 = enrolledUser.makeDevice();
auto enrolledUserLaptop = device1.createCore();

TC_AWAIT(server->enrollUser(enrolledUser.identity, {emailVerification, phoneNumberVerification, oidcVerification}));
TC_AWAIT(server->enrollUser(enrolledUser.identity, {emailVerification, phoneNumberVerification, oidcVerification, prehashedAndEncryptedPassphraseVerification}));
auto verificationCode = TC_AWAIT(getVerificationCode(verifEmail));

auto const disposableIdentity = trustchain.makeUser();
Expand All @@ -1320,6 +1375,8 @@ TEST_CASE_METHOD(TrustchainFixture, "User enrollment")
auto enrolledUserPhone = device2.createCore();
auto device3 = enrolledUser.makeDevice();
auto enrolledUserTablet = device3.createCore();
auto device4 = enrolledUser.makeDevice();
auto enrolledUserLaptop2 = device4.createCore();

REQUIRE(TC_AWAIT(enrolledUserLaptop->start(enrolledUser.identity)) == Status::IdentityVerificationNeeded);
REQUIRE_NOTHROW(
Expand All @@ -1334,9 +1391,12 @@ TEST_CASE_METHOD(TrustchainFixture, "User enrollment")
auto testNonce = TC_AWAIT(enrolledUserTablet->createOidcNonce());
enrolledUserTablet->setOidcTestNonce(testNonce);
REQUIRE_NOTHROW(TC_AWAIT(enrolledUserTablet->verifyIdentity(martineIdToken)));

REQUIRE(TC_AWAIT(enrolledUserLaptop2->start(enrolledUser.identity)) == Status::IdentityVerificationNeeded);
REQUIRE_NOTHROW(TC_AWAIT(enrolledUserLaptop2->verifyIdentity(Passphrase{clearPassphrase})));
}

SECTION("can attache a provisional identity")
SECTION("can attach a provisional identity")
{
std::vector<uint8_t> encryptedData = TC_AWAIT(encrypt(*server, clearData, {provisionalIdentity.publicIdentity}));

Expand Down
6 changes: 4 additions & 2 deletions modules/sdk-c/include/ctanker/ctanker.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ enum tanker_verification_method_type
TANKER_VERIFICATION_METHOD_E2E_PASSPHRASE,
TANKER_VERIFICATION_METHOD_PREVERIFIED_OIDC,
TANKER_VERIFICATION_METHOD_OIDC_AUTHORIZATION_CODE,
TANKER_VERIFICATION_METHOD_PREHASHED_AND_ENCRYPTED_PASSPHRASE,

TANKER_VERIFICATION_METHOD_LAST,
};
Expand Down Expand Up @@ -211,12 +212,13 @@ struct tanker_verification
char const* preverified_phone_number;
tanker_preverified_oidc_verification_t preverified_oidc_verification;
tanker_oidc_authorization_code_verification_t oidc_authorization_code_verification;
char const* prehashed_and_encrypted_passphrase;
};

#define TANKER_VERIFICATION_INIT \
{ \
8, 0, NULL, TANKER_EMAIL_VERIFICATION_INIT, NULL, NULL, NULL, TANKER_PHONE_NUMBER_VERIFICATION_INIT, NULL, NULL, \
TANKER_PREVERIFIED_OIDC_VERIFICATION_INIT, TANKER_OIDC_AUTHORIZATION_CODE_INIT \
9, 0, NULL, TANKER_EMAIL_VERIFICATION_INIT, NULL, NULL, NULL, TANKER_PHONE_NUMBER_VERIFICATION_INIT, NULL, NULL, \
TANKER_PREVERIFIED_OIDC_VERIFICATION_INIT, TANKER_OIDC_AUTHORIZATION_CODE_INIT, NULL \
}

struct tanker_verification_method
Expand Down
11 changes: 9 additions & 2 deletions modules/sdk-c/src/ctanker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Verification::Verification cverificationToVerification(tanker_verification_t con
{
throw formatEx(Errc::InvalidArgument, "no verification method specified in the tanker_verification_t struct");
}
if (cverification->version != 8)
if (cverification->version != 9)
{
throw formatEx(
Errc::InvalidArgument, "unsupported tanker_verification_t struct version: {}", cverification->version);
Expand Down Expand Up @@ -129,6 +129,12 @@ Verification::Verification cverificationToVerification(tanker_verification_t con
cverification->oidc_authorization_code_verification.state};
break;
}
case TANKER_VERIFICATION_METHOD_PREHASHED_AND_ENCRYPTED_PASSPHRASE: {
if (!cverification->prehashed_and_encrypted_passphrase)
throw formatEx(Errc::InvalidArgument, "prehashed_and_encrypted_passphrase field is null");
verification = PrehashedAndEncryptedPassphrase{cverification->prehashed_and_encrypted_passphrase};
break;
}
default:
throw formatEx(Errc::InvalidArgument, "unknown verification type");
}
Expand Down Expand Up @@ -230,9 +236,10 @@ STATIC_ENUM_CHECK(TANKER_VERIFICATION_METHOD_PREVERIFIED_EMAIL, Verification::Me
STATIC_ENUM_CHECK(TANKER_VERIFICATION_METHOD_PREVERIFIED_PHONE_NUMBER, Verification::Method::PreverifiedPhoneNumber);
STATIC_ENUM_CHECK(TANKER_VERIFICATION_METHOD_PREVERIFIED_OIDC, Verification::Method::PreverifiedOidc);
STATIC_ENUM_CHECK(TANKER_VERIFICATION_METHOD_OIDC_AUTHORIZATION_CODE, Verification::Method::OidcAuthorizationCode);
STATIC_ENUM_CHECK(TANKER_VERIFICATION_METHOD_PREHASHED_AND_ENCRYPTED_PASSPHRASE, Verification::Method::PrehashedAndEncryptedPassphrase);
STATIC_ENUM_CHECK(TANKER_VERIFICATION_METHOD_LAST, Verification::Method::Last);

static_assert(TANKER_VERIFICATION_METHOD_LAST == 11,
static_assert(TANKER_VERIFICATION_METHOD_LAST == 12,
"Please update the assertions above if you added a new "
"unlock method");

Expand Down
1 change: 1 addition & 0 deletions modules/sdk-core/include/Tanker/Verification/Methods.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum class Method
E2ePassphrase,
PreverifiedOidc,
OidcAuthorizationCode,
PrehashedAndEncryptedPassphrase,

Last,
};
Expand Down
7 changes: 5 additions & 2 deletions modules/sdk-core/include/Tanker/Verification/Request.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <Tanker/Types/EncryptedVerificationKeyForUserSecret.hpp>
#include <Tanker/Types/OidcChallenge.hpp>
#include <Tanker/Types/PhoneNumber.hpp>
#include <Tanker/Types/PrehashedAndEncryptedPassphrase.hpp>
#include <Tanker/Types/VerificationCode.hpp>
#include <Tanker/Verification/Verification.hpp>

Expand Down Expand Up @@ -71,7 +72,8 @@ using RequestVerification = boost::variant2::variant<VerificationKey,
PreverifiedEmail,
PreverifiedPhoneNumber,
PreverifiedOidc,
OidcAuthorizationCode>;
OidcAuthorizationCode,
PrehashedAndEncryptedPassphrase>;

using RequestVerificationPayload = boost::variant2::variant<VerificationKey,
EncryptedEmailVerification,
Expand All @@ -83,7 +85,8 @@ using RequestVerificationPayload = boost::variant2::variant<VerificationKey,
EncryptedPreverifiedEmailVerification,
EncryptedPreverifiedPhoneNumberVerification,
PreverifiedOidc,
OidcAuthorizationCode>;
OidcAuthorizationCode,
PrehashedAndEncryptedPassphrase>;

struct RequestWithVerif
{
Expand Down
7 changes: 5 additions & 2 deletions modules/sdk-core/include/Tanker/Verification/Verification.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <Tanker/Types/OidcNonce.hpp>
#include <Tanker/Types/Passphrase.hpp>
#include <Tanker/Types/PhoneNumber.hpp>
#include <Tanker/Types/PrehashedAndEncryptedPassphrase.hpp>
#include <Tanker/Types/PreverifiedEmail.hpp>
#include <Tanker/Types/PreverifiedOidc.hpp>
#include <Tanker/Types/PreverifiedPhoneNumber.hpp>
Expand Down Expand Up @@ -53,7 +54,8 @@ using Verification = boost::variant2::variant<VerificationKey,
PreverifiedEmail,
PreverifiedPhoneNumber,
PreverifiedOidc,
OidcAuthorizationCode>;
OidcAuthorizationCode,
PrehashedAndEncryptedPassphrase>;

class VerificationMethod
{
Expand All @@ -67,7 +69,8 @@ class VerificationMethod
PreverifiedEmail,
PreverifiedPhoneNumber,
PreverifiedOidc,
OidcAuthorizationCode))
OidcAuthorizationCode,
PrehashedAndEncryptedPassphrase))

static VerificationMethod from(Verification const& v);

Expand Down
10 changes: 8 additions & 2 deletions modules/sdk-core/src/Core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,15 +403,17 @@ tc::cotask<void> Core::enrollUser(std::string const& b64Identity,
});
uint64_t nbOidc = ranges::count_if(
verifications, [](auto const& verif) { return boost::variant2::holds_alternative<PreverifiedOidc>(verif); });
uint64_t nbPAEP = ranges::count_if(
verifications, [](auto const& verif) { return boost::variant2::holds_alternative<PrehashedAndEncryptedPassphrase>(verif); });

if (nbEmails + nbPhones + nbOidc != verifications.size())
if (nbEmails + nbPhones + nbOidc + nbPAEP != verifications.size())
{
throw formatEx(Errc::InvalidArgument,
"verifications: can only enroll user with preverified "
"verification methods");
}

if (nbEmails > 1 || nbPhones > 1 || nbOidc > 1)
if (nbEmails > 1 || nbPhones > 1 || nbOidc > 1 || nbPAEP > 1)
{
throw formatEx(Errc::InvalidArgument,
"verifications: contains at most one of each preverified "
Expand Down Expand Up @@ -787,6 +789,10 @@ tc::cotask<std::optional<std::string>> Core::setVerificationMethod(Verification:
{
throw formatEx(Errc::InvalidArgument, "cannot call setVerificationMethod with a verification key");
}
if (boost::variant2::holds_alternative<PrehashedAndEncryptedPassphrase>(method))
{
throw formatEx(Errc::InvalidArgument, "cannot call setVerificationMethod with a prehashed and encrypted passphrase");
}
auto withTokenNonce = makeWithTokenRandomNonce(withToken);

auto const& localUser = _session->accessors().localUserAccessor.get();
Expand Down
3 changes: 3 additions & 0 deletions modules/sdk-core/src/Users/EntryGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ Trustchain::Actions::VerificationMethodType verificationMethodType(Verification:
[](OidcAuthorizationCode const& v) -> VerificationMethodType {
throw Errors::AssertionError("No verification method type for oidc authorization code");
},
[](PrehashedAndEncryptedPassphrase const& v) -> VerificationMethodType {
throw Errors::AssertionError("No verification method type for prehashed and encrypted password");
},
},
verification);
}
Expand Down
8 changes: 8 additions & 0 deletions modules/sdk-core/src/Verification/Request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ RequestWithVerif makeRequestWithVerif(RequestVerification const& verification,
checkNotEmpty(v.state, "oidcState");
return v;
},
[&](PrehashedAndEncryptedPassphrase const& v) -> RequestVerificationPayload {
checkNotEmpty(v.string(), "prehashed_and_encrypted_passphrase");
return v;
},
},
verification);
return {verif, withTokenNonce};
Expand Down Expand Up @@ -282,6 +286,10 @@ void adl_serializer<Tanker::Verification::RequestVerificationPayload>::to_json(
j["oidc_provider_id"] = o.provider_id;
j["oidc_authorization_code"] = o.authorization_code;
j["oidc_state"] = o.state;
},
[&](PrehashedAndEncryptedPassphrase const& p) {
j["prehashed_and_encrypted_passphrase"] = p;
j["is_preverified"] = true;
}},
request);
}
Expand Down
5 changes: 3 additions & 2 deletions modules/sdk-core/src/Verification/Verification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ VerificationMethod VerificationMethod::from(Verification const& v)
[](PreverifiedEmail const& v) -> VerificationMethod { return v; },
[](PreverifiedPhoneNumber const& v) -> VerificationMethod { return v; },
[](PreverifiedOidc const& v) -> VerificationMethod { return v; },
[](OidcAuthorizationCode const& v) -> VerificationMethod { return v; }},
[](OidcAuthorizationCode const& v) -> VerificationMethod { return v; },
[](PrehashedAndEncryptedPassphrase const& v) -> VerificationMethod { return v; }},
v);
}

Expand Down Expand Up @@ -158,7 +159,7 @@ bool isPreverified(Verification const& v)
{
using boost::variant2::holds_alternative;
return holds_alternative<PreverifiedEmail>(v) || holds_alternative<PreverifiedPhoneNumber>(v) ||
holds_alternative<PreverifiedOidc>(v);
holds_alternative<PreverifiedOidc>(v) || holds_alternative<PrehashedAndEncryptedPassphrase>(v) ;
}

bool isE2eVerification(Verification const& v)
Expand Down
3 changes: 3 additions & 0 deletions modules/test-helpers/include/Helpers/Config.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <Tanker/Crypto/PublicEncryptionKey.hpp>

#include <chrono>
#include <map>
#include <string>
Expand Down Expand Up @@ -39,5 +41,6 @@ std::string const& trustchaindUrl();
std::string const& verificationApiToken();
OidcConfig const& oidcConfig();
std::chrono::minutes maxExecutionTimeout();
Tanker::Crypto::PublicEncryptionKey const passphrasePublicEncryptionKey();
}
}
6 changes: 6 additions & 0 deletions modules/test-helpers/src/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,11 @@ std::string const& verificationApiToken()
static auto const value = getSafeEnv("TANKER_VERIFICATION_API_TEST_TOKEN");
return value;
}

Tanker::Crypto::PublicEncryptionKey const passphrasePublicEncryptionKey()
{
static auto const value = getSafeEnv("TANKER_ENROLL_PASSPHRASE_PUBLIC_ENCRYPTION_KEY");
return mgs::base64::decode<Tanker::Crypto::PublicEncryptionKey>(value);
}
}
}
1 change: 1 addition & 0 deletions modules/types/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ target_sources(tankertypes INTERFACE
${PROJECT_SOURCE_DIR}/include/Tanker/Types/OidcNonce.hpp
${PROJECT_SOURCE_DIR}/include/Tanker/Types/Overloaded.hpp
${PROJECT_SOURCE_DIR}/include/Tanker/Types/Passphrase.hpp
${PROJECT_SOURCE_DIR}/include/Tanker/Types/PrehashedAndEncryptedPassphrase.hpp
${PROJECT_SOURCE_DIR}/include/Tanker/Types/PreverifiedOidc.hpp
${PROJECT_SOURCE_DIR}/include/Tanker/Types/ProvisionalUserKeys.hpp
${PROJECT_SOURCE_DIR}/include/Tanker/Types/SDeviceId.hpp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once

#include <Tanker/Types/StringWrapper.hpp>

namespace Tanker
{
using PrehashedAndEncryptedPassphrase = StringWrapper<struct PrehashedAndEncryptedPassphraseTag>;
}
Loading

0 comments on commit e47b662

Please sign in to comment.