diff --git a/fdbrpc/TokenSign.cpp b/fdbrpc/TokenSign.cpp index 05fb4db8b6..2b2640442d 100644 --- a/fdbrpc/TokenSign.cpp +++ b/fdbrpc/TokenSign.cpp @@ -139,15 +139,6 @@ namespace authz { using MessageDigestMethod = const EVP_MD*; -Algorithm algorithmFromString(StringRef s) noexcept { - if (s == "RS256"_sr) - return Algorithm::RS256; - else if (s == "ES256"_sr) - return Algorithm::ES256; - else - return Algorithm::UNKNOWN; -} - std::pair getMethod(Algorithm alg) { if (alg == Algorithm::RS256) { return { PKeyAlgorithm::RSA, ::EVP_sha256() }; @@ -169,36 +160,6 @@ std::string_view getAlgorithmName(Algorithm alg) { } // namespace authz -namespace authz::flatbuffers { - -SignedTokenRef signToken(Arena& arena, TokenRef token, StringRef keyName, PrivateKey privateKey) { - auto ret = SignedTokenRef{}; - auto writer = ObjectWriter([&arena](size_t len) { return new (arena) uint8_t[len]; }, IncludeVersion()); - writer.serialize(token); - auto tokenStr = writer.toStringRef(); - auto sig = privateKey.sign(arena, tokenStr, *::EVP_sha256()); - ret.token = tokenStr; - ret.signature = sig; - ret.keyName = StringRef(arena, keyName); - return ret; -} - -bool verifyToken(SignedTokenRef signedToken, PublicKey publicKey) { - return publicKey.verify(signedToken.token, signedToken.signature, *::EVP_sha256()); -} - -TokenRef makeRandomTokenSpec(Arena& arena, IRandom& rng) { - auto token = TokenRef{}; - token.expiresAt = timer_monotonic() * (0.5 + rng.random01()); - const auto numTenants = rng.randomInt(1, 3); - for (auto i = 0; i < numTenants; i++) { - token.tenants.push_back(arena, genRandomAlphanumStringRef(arena, rng, MinTenantNameLen, MaxTenantNameLenPlus1)); - } - return token; -} - -} // namespace authz::flatbuffers - namespace authz::jwt { template @@ -378,7 +339,7 @@ Optional parseHeaderPart(Arena& arena, TokenRef& token, StringRef b64 if (typValue != "JWT"_sr) return "'typ' is not 'JWT'"_sr; auto algValue = StringRef(reinterpret_cast(alg.GetString()), alg.GetStringLength()); - auto algType = algorithmFromString(algValue); + auto algType = algorithmFromString(algValue.toStringView()); if (algType == Algorithm::UNKNOWN) return "Unsupported algorithm"_sr; token.algorithm = algType; @@ -582,29 +543,6 @@ TokenRef makeRandomTokenSpec(Arena& arena, IRandom& rng, Algorithm alg) { void forceLinkTokenSignTests() {} -TEST_CASE("/fdbrpc/TokenSign/FlatBuffer") { - const auto numIters = 100; - for (auto i = 0; i < numIters; i++) { - auto arena = Arena(); - auto privateKey = mkcert::makeEcP256(); - auto publicKey = privateKey.toPublic(); - auto& rng = *deterministicRandom(); - auto tokenSpec = authz::flatbuffers::makeRandomTokenSpec(arena, rng); - auto keyName = genRandomAlphanumStringRef(arena, rng, MinKeyNameLen, MaxKeyNameLenPlus1); - auto signedToken = authz::flatbuffers::signToken(arena, tokenSpec, keyName, privateKey); - ASSERT(authz::flatbuffers::verifyToken(signedToken, publicKey)); - // try tampering with signed token by adding one more tenant - tokenSpec.tenants.push_back(arena, - genRandomAlphanumStringRef(arena, rng, MinTenantNameLen, MaxTenantNameLenPlus1)); - auto writer = ObjectWriter([&arena](size_t len) { return new (arena) uint8_t[len]; }, IncludeVersion()); - writer.serialize(tokenSpec); - signedToken.token = writer.toStringRef(); - ASSERT(!authz::flatbuffers::verifyToken(signedToken, publicKey)); - } - printf("%d runs OK\n", numIters); - return Void(); -} - TEST_CASE("/fdbrpc/TokenSign/JWT") { const auto numIters = 100; for (auto i = 0; i < numIters; i++) { @@ -685,73 +623,42 @@ TEST_CASE("/fdbrpc/TokenSign/JWT/ToStringRef") { return Void(); } -// This unit test takes too long to run in RandomUnitTests.toml -// FIXME: Move this to benchmark to flowbench -/* TEST_CASE("/fdbrpc/TokenSign/bench") { - auto keyTypes = std::array{ "EC"_sr, "RSA"_sr }; - for (auto kty : keyTypes) { - constexpr auto repeat = 5; - constexpr auto numSamples = 10000; - fmt::print("=== {} keys case\n", kty.toString()); - auto key = kty == "EC"_sr ? mkcert::makeEcP256() : mkcert::makeRsa4096Bit(); - auto pubKey = key.toPublic(); - auto& rng = *deterministicRandom(); - auto arena = Arena(); - auto jwtSpecs = new (arena) authz::jwt::TokenRef[numSamples]; - auto fbSpecs = new (arena) authz::flatbuffers::TokenRef[numSamples]; - auto jwts = new (arena) StringRef[numSamples]; - auto fbs = new (arena) StringRef[numSamples]; - for (auto i = 0; i < numSamples; i++) { - jwtSpecs[i] = authz::jwt::makeRandomTokenSpec( - arena, rng, kty == "EC"_sr ? authz::Algorithm::ES256 : authz::Algorithm::RS256); - fbSpecs[i] = authz::flatbuffers::makeRandomTokenSpec(arena, rng); - } - { - auto const jwtSignBegin = timer_monotonic(); - for (auto i = 0; i < numSamples; i++) { - jwts[i] = authz::jwt::signToken(arena, jwtSpecs[i], key); - } - auto const jwtSignEnd = timer_monotonic(); - fmt::print("JWT Sign : {:.2f} OPS\n", numSamples / (jwtSignEnd - jwtSignBegin)); - } - { - auto const jwtVerifyBegin = timer_monotonic(); - for (auto rep = 0; rep < repeat; rep++) { - for (auto i = 0; i < numSamples; i++) { - auto verifyOk = authz::jwt::verifyToken(jwts[i], pubKey); - ASSERT(verifyOk); - } - } - auto const jwtVerifyEnd = timer_monotonic(); - fmt::print("JWT Verify : {:.2f} OPS\n", repeat * numSamples / (jwtVerifyEnd - jwtVerifyBegin)); - } - { - auto tmpArena = Arena(); - auto const fbSignBegin = timer_monotonic(); - for (auto i = 0; i < numSamples; i++) { - auto fbToken = authz::flatbuffers::signToken(tmpArena, fbSpecs[i], "defaultKey"_sr, key); - auto wr = ObjectWriter([&arena](size_t len) { return new (arena) uint8_t[len]; }, Unversioned()); - wr.serialize(fbToken); - fbs[i] = wr.toStringRef(); - } - auto const fbSignEnd = timer_monotonic(); - fmt::print("FlatBuffers Sign : {:.2f} OPS\n", numSamples / (fbSignEnd - fbSignBegin)); - } - { - auto const fbVerifyBegin = timer_monotonic(); - for (auto rep = 0; rep < repeat; rep++) { - for (auto i = 0; i < numSamples; i++) { - auto signedToken = ObjectReader::fromStringRef>( - fbs[i], Unversioned()); - auto verifyOk = authz::flatbuffers::verifyToken(signedToken, pubKey); - ASSERT(verifyOk); - } - } - auto const fbVerifyEnd = timer_monotonic(); - fmt::print("FlatBuffers Verify : {:.2f} OPS\n", repeat * numSamples / (fbVerifyEnd - fbVerifyBegin)); - } - } - return Void(); + auto keyTypes = std::array{ "EC"_sr }; + for (auto kty : keyTypes) { + constexpr auto repeat = 5; + constexpr auto numSamples = 10000; + fmt::print("=== {} keys case\n", kty.toString()); + auto key = kty == "EC"_sr ? mkcert::makeEcP256() : mkcert::makeRsa4096Bit(); + auto pubKey = key.toPublic(); + auto& rng = *deterministicRandom(); + auto arena = Arena(); + auto jwtSpecs = new (arena) authz::jwt::TokenRef[numSamples]; + auto jwts = new (arena) StringRef[numSamples]; + for (auto i = 0; i < numSamples; i++) { + jwtSpecs[i] = authz::jwt::makeRandomTokenSpec( + arena, rng, kty == "EC"_sr ? authz::Algorithm::ES256 : authz::Algorithm::RS256); + } + { + auto const jwtSignBegin = timer_monotonic(); + for (auto i = 0; i < numSamples; i++) { + jwts[i] = authz::jwt::signToken(arena, jwtSpecs[i], key); + } + auto const jwtSignEnd = timer_monotonic(); + fmt::print("JWT Sign : {:.2f} OPS\n", numSamples / (jwtSignEnd - jwtSignBegin)); + } + { + auto const jwtVerifyBegin = timer_monotonic(); + for (auto rep = 0; rep < repeat; rep++) { + for (auto i = 0; i < numSamples; i++) { + auto [verifyOk, errorMsg] = authz::jwt::verifyToken(jwts[i], pubKey); + ASSERT(!errorMsg.present()); + ASSERT(verifyOk); + } + } + auto const jwtVerifyEnd = timer_monotonic(); + fmt::print("JWT Verify : {:.2f} OPS\n", repeat * numSamples / (jwtVerifyEnd - jwtVerifyBegin)); + } + } + return Void(); } -*/ diff --git a/fdbrpc/TokenSign/TokenSignStdTypes.cpp b/fdbrpc/TokenSign/TokenSignStdTypes.cpp index 83c43d2b25..6abd4055a2 100644 --- a/fdbrpc/TokenSign/TokenSignStdTypes.cpp +++ b/fdbrpc/TokenSign/TokenSignStdTypes.cpp @@ -18,6 +18,7 @@ * limitations under the License. */ +#include "fdbrpc/TokenSign.h" #include "fdbrpc/TokenSignStdTypes.h" #include "flow/PKey.h" #include "flow/MkCert.h" diff --git a/fdbrpc/include/fdbrpc/TokenSign.h b/fdbrpc/include/fdbrpc/TokenSign.h index b833884794..60ee042282 100644 --- a/fdbrpc/include/fdbrpc/TokenSign.h +++ b/fdbrpc/include/fdbrpc/TokenSign.h @@ -18,94 +18,21 @@ * limitations under the License. */ -#pragma once #ifndef FDBRPC_TOKEN_SIGN_H #define FDBRPC_TOKEN_SIGN_H +#pragma once #include "flow/network.h" #include "flow/Arena.h" #include "flow/FileIdentifier.h" #include "flow/PKey.h" +#include "fdbrpc/TokenSpec.h" #include #include -namespace authz { - -enum class Algorithm : int { - RS256, - ES256, - UNKNOWN, -}; - -Algorithm algorithmFromString(StringRef s) noexcept; - -} // namespace authz - -namespace authz::flatbuffers { - -struct TokenRef { - static constexpr FileIdentifier file_identifier = 1523118; - double expiresAt; - VectorRef tenants; - - template - void serialize(Ar& ar) { - serializer(ar, expiresAt, tenants); - } -}; - -struct SignedTokenRef { - static constexpr FileIdentifier file_identifier = 5916732; - StringRef token; - StringRef keyName; - StringRef signature; - - template - void serialize(Ar& ar) { - serializer(ar, token, keyName, signature); - } - - int expectedSize() const { return token.size() + keyName.size() + signature.size(); } -}; - -SignedTokenRef signToken(Arena& arena, TokenRef token, StringRef keyName, PrivateKey privateKey); - -bool verifyToken(SignedTokenRef signedToken, PublicKey publicKey); - -} // namespace authz::flatbuffers - namespace authz::jwt { -// Given S = concat(B64UrlEnc(headerJson), ".", B64UrlEnc(payloadJson)), -// JWT is concat(S, ".", B64UrlEnc(sign(S, PrivateKey))). -// Below we refer to S as "sign input" - -// This struct is not meant to be flatbuffer-serialized -// This is a parsed, flattened view of S and signature -template -struct BasicTokenSpec { - using StringType = std::conditional_t; - template - using VectorType = std::conditional_t, std::vector>; - template - using OptionalType = std::conditional_t, std::optional>; - // header part ("typ": "JWT" implicitly enforced) - Algorithm algorithm; // alg - StringType keyId; // kid - // payload part - OptionalType issuer; // iss - OptionalType subject; // sub - OptionalType> audience; // aud - OptionalType issuedAtUnixTime; // iat - OptionalType expiresAtUnixTime; // exp - OptionalType notBeforeUnixTime; // nbf - OptionalType tokenId; // jti - OptionalType> tenants; // tenants - // signature part - StringType signature; -}; - -using TokenRef = BasicTokenSpec; +using TokenRef = BasicTokenSpec; // print each non-signature field in non-JSON, human-readable format e.g. for trace StringRef toStringRef(Arena& arena, const TokenRef& tokenSpec); diff --git a/fdbrpc/include/fdbrpc/TokenSignStdTypes.h b/fdbrpc/include/fdbrpc/TokenSignStdTypes.h index ea69a63613..88e94fc6a6 100644 --- a/fdbrpc/include/fdbrpc/TokenSignStdTypes.h +++ b/fdbrpc/include/fdbrpc/TokenSignStdTypes.h @@ -20,8 +20,9 @@ #pragma once #ifndef FDBRPC_TOKEN_SIGN_STD_TYPES_H #define FDBRPC_TOKEN_SIGN_STD_TYPES_H -#include "fdbrpc/TokenSign.h" +#include "fdbrpc/TokenSpec.h" #include +#include // Below functions build as a library separate from fdbrpc // The intent is to re-use the key/token generation part in a way that the input, the output, @@ -30,7 +31,7 @@ namespace authz::jwt::stdtypes { -using TokenSpec = BasicTokenSpec; +using TokenSpec = BasicTokenSpec; // Generate an elliptic curve private key on a P-256 curve, and serialize it as PEM. std::string makeEcP256PrivateKeyPem(); diff --git a/fdbrpc/include/fdbrpc/TokenSpec.h b/fdbrpc/include/fdbrpc/TokenSpec.h new file mode 100644 index 0000000000..0401d11c61 --- /dev/null +++ b/fdbrpc/include/fdbrpc/TokenSpec.h @@ -0,0 +1,77 @@ +/* + * TokenSpec.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors + * + * 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 FDBRPC_TOKEN_SPEC_H +#define FDBRPC_TOKEN_SPEC_H +#pragma once + +#include +#include +#include + +namespace authz { + +enum class Algorithm : int { + RS256, + ES256, + UNKNOWN, +}; + +inline Algorithm algorithmFromString(std::string_view s) noexcept { + if (s == "RS256") + return Algorithm::RS256; + else if (s == "ES256") + return Algorithm::ES256; + else + return Algorithm::UNKNOWN; +} + +} // namespace authz + +namespace authz::jwt { + +// Given S = concat(B64UrlEnc(headerJson), ".", B64UrlEnc(payloadJson)), +// JWT is concat(S, ".", B64UrlEnc(sign(S, PrivateKey))). +// Below we refer to S as "sign input" + +// This struct is not meant to be flatbuffer-serialized +// This is a parsed, flattened view of S and signature + +template class VectorType, template class OptionalType = std::optional> +struct BasicTokenSpec { + // header part ("typ": "JWT" implicitly enforced) + Algorithm algorithm; // alg + StringType keyId; // kid + // payload part + OptionalType issuer; // iss + OptionalType subject; // sub + OptionalType> audience; // aud + OptionalType issuedAtUnixTime; // iat + OptionalType expiresAtUnixTime; // exp + OptionalType notBeforeUnixTime; // nbf + OptionalType tokenId; // jti + OptionalType> tenants; // tenants + // signature part + StringType signature; +}; + +} // namespace authz::jwt + +#endif /*FDBRPC_TOKEN_SPEC_H*/ diff --git a/fdbserver/workloads/UnitTests.actor.cpp b/fdbserver/workloads/UnitTests.actor.cpp index bf2c4394ee..2286a7e9e1 100644 --- a/fdbserver/workloads/UnitTests.actor.cpp +++ b/fdbserver/workloads/UnitTests.actor.cpp @@ -45,6 +45,7 @@ void forceLinkCompressionUtilsTest(); void forceLinkAtomicTests(); void forceLinkIdempotencyIdTests(); void forceLinkBlobConnectionProviderTests(); +void forceLinkArenaStringTests(); struct UnitTestWorkload : TestWorkload { static constexpr auto NAME = "UnitTests"; @@ -106,6 +107,7 @@ struct UnitTestWorkload : TestWorkload { forceLinkAtomicTests(); forceLinkIdempotencyIdTests(); forceLinkBlobConnectionProviderTests(); + forceLinkArenaStringTests(); } Future setup(Database const& cx) override { diff --git a/flow/ArenaString.cpp b/flow/ArenaString.cpp new file mode 100644 index 0000000000..9e9b7d3e42 --- /dev/null +++ b/flow/ArenaString.cpp @@ -0,0 +1,49 @@ +#include "flow/UnitTest.h" +#include "flow/ArenaAllocator.h" +#include "flow/ArenaString.h" + +TEST_CASE("/flow/ArenaString") { + Arena arena; + ArenaAllocator alloc(arena); + { + ArenaString s("1", alloc); + auto shortStrBuf = s.data(); + s.assign(100, '1'); + auto longStrBuf = s.data(); + ASSERT_NE(shortStrBuf, longStrBuf); + ArenaString t = s; + auto copiedStrBuf = t.data(); + ASSERT_NE(copiedStrBuf, longStrBuf); + } + { + ArenaString s(alloc); + s.assign(100, 'a'); + ArenaString t(100, 'a', alloc); + ASSERT(s == t); + } + { + // Default construction of string does not specify an allocator, and Arena by extension. + // Any modification that requires allocation will throw bad_allocator() when assigning beyond + // short-string-optimized length. + ArenaString s; + bool hit = false; + try { + s.assign(100, 'a'); + } catch (Error& e) { + hit = true; + ASSERT_EQ(e.code(), error_code_bad_allocator); + } + ASSERT(hit); + } + { + // string_view may be used to bridge strings with different allocators + ArenaString s(100, 'a', alloc); + std::string_view sv(s); + std::string s2(sv); + std::string_view sv2(s2); + ASSERT(sv == sv2); + } + return Void(); +} + +void forceLinkArenaStringTests() {} diff --git a/flow/include/flow/ArenaAllocator.h b/flow/include/flow/ArenaAllocator.h new file mode 100644 index 0000000000..3d90891894 --- /dev/null +++ b/flow/include/flow/ArenaAllocator.h @@ -0,0 +1,90 @@ +/* + * ArenaAllocator.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors + * + * 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 FLOW_ARENA_ALLOCATOR_H +#define FLOW_ARENA_ALLOCATOR_H +#pragma once + +#include "flow/Arena.h" +#include "flow/Error.h" +#include "flow/FastRef.h" +#include +#include +#include + +template +class ArenaAllocator { + Arena* arenaPtr; + + Arena& arena() noexcept { return *arenaPtr; } + +public: + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using void_pointer = void*; + using const_void_pointer = const void*; + using self_type = ArenaAllocator; + using size_type = size_t; + using value_type = T; + using difference_type = typename std::pointer_traits::difference_type; + + // Unfortunately this needs to exist due to STL's internal use of Allocator() in internal coding + ArenaAllocator() noexcept : arenaPtr(nullptr) {} + + ArenaAllocator(Arena& arena) noexcept : arenaPtr(&arena) {} + + ArenaAllocator(const self_type& other) noexcept = default; + + // Rebind constructor does not modify + template + ArenaAllocator(const ArenaAllocator& other) noexcept : arenaPtr(other.arenaPtr) {} + + ArenaAllocator& operator=(const self_type& other) noexcept = default; + + ArenaAllocator(self_type&& other) noexcept = default; + + ArenaAllocator& operator=(self_type&& other) noexcept = default; + + T* allocate(size_t n) { + if (!arenaPtr) + throw bad_allocator(); + return new (arena()) T[n]; + } + + void deallocate(T*, size_t) noexcept {} + + bool operator==(const self_type& other) const noexcept { return arenaPtr == other.arenaPtr; } + + bool operator!=(const self_type& other) const noexcept { return !(*this == other); } + + template + struct rebind { + using other = ArenaAllocator; + }; + + using is_always_equal = std::false_type; + using propagate_on_container_copy_assignment = std::true_type; + using propagate_on_container_move_assignment = std::true_type; + using propagate_on_container_swap = std::true_type; +}; + +#endif /*FLOW_ARENA_ALLOCATOR_H*/ diff --git a/flow/include/flow/ArenaString.h b/flow/include/flow/ArenaString.h new file mode 100644 index 0000000000..c2fd2f886f --- /dev/null +++ b/flow/include/flow/ArenaString.h @@ -0,0 +1,30 @@ +/* + * ArenaString.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors + * + * 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 FLOW_ARENA_STRING_H +#define FLOW_ARENA_STRING_H +#pragma once + +#include "flow/ArenaAllocator.h" +#include "flow/CustomAllocatorString.h" + +using ArenaString = CustomAllocatorString; + +#endif /*FLOW_ARENA_STRING_H*/ diff --git a/flow/include/flow/CustomAllocatorString.h b/flow/include/flow/CustomAllocatorString.h new file mode 100644 index 0000000000..3b8249d965 --- /dev/null +++ b/flow/include/flow/CustomAllocatorString.h @@ -0,0 +1,30 @@ +/* + * CustomAllocatorString.h + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2013-2022 Apple Inc. and the FoundationDB project authors + * + * 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 FLOW_CUSTOM_ALLOCATOR_STRING_H +#define FLOW_CUSTOM_ALLOCATOR_STRING_H +#pragma once + +#include + +template