diff --git a/ci/common.sh b/ci/common.sh
index 0f1a9636e62fec967d12e33c45eff465926db984..245f597705a0d9c298588d122513e7b7f5892466 100644
--- a/ci/common.sh
+++ b/ci/common.sh
@@ -120,9 +120,11 @@ generate_package_map() {
   local ducc="$8"
   local fuse3="$9"
   local gateway="${10}"
+  local libs="${11}"
 
   cat > pkgmap.${platform} << EOF
 [$platform]
+libs=$libs
 client=$client
 server=$server
 devel=$devel
diff --git a/ci/cvmfs/deb.sh b/ci/cvmfs/deb.sh
index f5e7662c752c8b39e096e10a90138a15943e409a..26724272d888a8c84f5daa1adcedeb7a6f75ee71 100755
--- a/ci/cvmfs/deb.sh
+++ b/ci/cvmfs/deb.sh
@@ -99,7 +99,8 @@ if [ ! -z $CVMFS_CI_PLATFORM_LABEL ]; then
                        "$(basename $(find . -name 'cvmfs-shrinkwrap*.deb'))"\
                        ""                                                   \
                        "$(basename $(find . -name 'cvmfs-fuse3*.deb'))"     \
-                       "$(basename $(find . -name 'cvmfs-gateway*.deb'))"
+                       "$(basename $(find . -name 'cvmfs-gateway*.deb'))"   \
+                       "$(basename $(find . -name 'cvmfs-libs*.deb'))"
 fi
 
 # clean up the source tree
diff --git a/ci/cvmfs/rpm.sh b/ci/cvmfs/rpm.sh
index 8d4d6520080bbd9510fa579bda7fa9bf0f4c919c..f1b7ff5447e0bfac596ab0e102129ad51ef02d5a 100755
--- a/ci/cvmfs/rpm.sh
+++ b/ci/cvmfs/rpm.sh
@@ -86,5 +86,6 @@ if [ ! -z $CVMFS_CI_PLATFORM_LABEL ]; then
     "$(basename $(find . -regex '.*cvmfs-shrinkwrap-[0-9].*\.rpm'))"          \
     "$(basename $(find . -regex '.*cvmfs-ducc-[0-9].*\.rpm'))"                \
     "$(basename $(find . -regex '.*cvmfs-fuse3-[0-9].*\.rpm'))"               \
-    "$(basename $(find . -regex '.*cvmfs-gateway-[0-9].*\.rpm'))"
+    "$(basename $(find . -regex '.*cvmfs-gateway-[0-9].*\.rpm'))"             \
+    "$(basename $(find . -regex '.*cvmfs-libs-[0-9].*\.rpm'))"
 fi
diff --git a/cvmfs/CMakeLists.txt b/cvmfs/CMakeLists.txt
index afa1be45bddb4f66e00d6c7282efa3ea55666ad1..6f1095d126951b10d154571658de6a8c63d4d7dd 100644
--- a/cvmfs/CMakeLists.txt
+++ b/cvmfs/CMakeLists.txt
@@ -2,6 +2,8 @@
 # This file is part of the CernVM File System
 #
 
+include (util/CMakeLists.txt)
+
 # /usr/bin/cvmfs2
 set (CVMFS2_BINARY_SOURCES
   fuse_main.cc
@@ -278,7 +280,6 @@ set (CVMFS_PUBLISH_SOURCES
   util/exception.cc
   util/namespace.cc
   util/posix.cc
-  util/string.cc
   whitelist.cc
 )
 
@@ -360,7 +361,6 @@ set (LIBCVMFS_SERVER_SOURCES
   util/mmap_file.cc
   util/posix.cc
   util/raii_temp_dir.cc
-  util/string.cc
   whitelist.cc
   xattr.cc
 )
@@ -474,7 +474,6 @@ set (CVMFS_SWISSKNIFE_SOURCES
   util/file_backed_buffer.cc
   util/mmap_file.cc
   util/posix.cc
-  util/string.cc
   util/raii_temp_dir.cc
   util_concurrency.cc
   whitelist.cc
@@ -489,7 +488,6 @@ set (CVMFS_SIGN_SOURCES
   signature.cc
   util/exception.cc
   util/posix.cc
-  util/string.cc
 )
 
 set (CVMFS_SUID_HELPER_SOURCES
@@ -643,7 +641,6 @@ if(BUILD_RECEIVER)
     util/file_backed_buffer.cc
     util/mmap_file.cc
     util/posix.cc
-    util/string.cc
     util/raii_temp_dir.cc
     util_concurrency.cc
     uuid.cc
@@ -652,7 +649,8 @@ if(BUILD_RECEIVER)
     )
 
   add_executable(cvmfs_receiver ${CVMFS_RECEIVER_SOURCES})
-  target_link_libraries(cvmfs_receiver ${CURL_LIBRARIES} ${CARES_LIBRARIES} ${CARES_LDFLAGS}
+  target_link_libraries(cvmfs_receiver cvmfs_util
+                        ${CURL_LIBRARIES} ${CARES_LIBRARIES} ${CARES_LDFLAGS}
                         ${SHA3_LIBRARIES} ${SQLITE3_LIBRARY} ${VJSON_LIBRARIES}
                         ${OPENSSL_LIBRARIES} ${UUID_LIBRARIES} ${ZLIB_LIBRARIES}
                         ${RT_LIBRARY} ${LibArchive_LIBRARY} pthread dl)
@@ -662,7 +660,7 @@ if(BUILD_RECEIVER)
 
   if(BUILD_RECEIVER_DEBUG)
     add_executable(cvmfs_receiver_debug ${CVMFS_RECEIVER_SOURCES})
-    target_link_libraries(cvmfs_receiver_debug
+    target_link_libraries(cvmfs_receiver_debug cvmfs_util_debug
                           ${CURL_LIBRARIES} ${CARES_LIBRARIES} ${CARES_LDFLAGS}
                           ${SHA3_LIBRARIES} ${SQLITE3_LIBRARY} ${VJSON_LIBRARIES}
                           ${OPENSSL_LIBRARIES} ${UUID_LIBRARIES} ${ZLIB_LIBRARIES}
@@ -985,13 +983,15 @@ if (BUILD_SERVER)
   set_target_properties (cvmfs_signing_helper PROPERTIES
                            COMPILE_FLAGS "${CVMFS_PUBLISH_CFLAGS}")
   target_link_libraries (cvmfs_signing_helper
+                         cvmfs_util
                          ${SHA3_LIBRARIES}
                          ${OPENSSL_LIBRARIES}
                          pthread dl)
 
   # link the stuff (*_LIBRARIES are dynamic link libraries)
   set (LIBCVMFS_SERVER_LINK_LIBRARIES "")
-  list(APPEND LIBCVMFS_SERVER_LINK_LIBRARIES ${CURL_LIBRARIES}
+  list(APPEND LIBCVMFS_SERVER_LINK_LIBRARIES
+                                             ${CURL_LIBRARIES}
                                              ${CARES_LIBRARIES} ${CARES_LDFLAGS}
                                              ${OPENSSL_LIBRARIES}
                                              ${SQLITE3_LIBRARY}
@@ -1002,18 +1002,18 @@ if (BUILD_SERVER)
                                              ${LibArchive_LIBRARY}
                                              ${RT_LIBRARY}
                                              pthread dl)
-  target_link_libraries (cvmfs_swissknife
+  target_link_libraries (cvmfs_swissknife cvmfs_util
                          ${SQLITE3_LIBRARY} ${CURL_LIBRARIES}
                          ${CARES_LIBRARIES} ${CARES_LDFLAGS} ${ZLIB_LIBRARIES}
                          ${OPENSSL_LIBRARIES} ${RT_LIBRARY} ${VJSON_LIBRARIES}
                          ${SHA3_LIBRARIES} ${CAP_LIBRARIES} ${LibArchive_LIBRARY}
                          pthread dl)
-  target_link_libraries (cvmfs_server ${LIBCVMFS_SERVER_LIBS} ${LIBCVMFS_SERVER_LINK_LIBRARIES})
-  target_link_libraries (cvmfs_publish cvmfs_server)
+  target_link_libraries (cvmfs_server cvmfs_util ${LIBCVMFS_SERVER_LIBS} ${LIBCVMFS_SERVER_LINK_LIBRARIES})
+  target_link_libraries (cvmfs_publish cvmfs_util cvmfs_server)
   if (BUILD_SERVER_DEBUG)
     add_executable (cvmfs_swissknife_debug ${CVMFS_SWISSKNIFE_SOURCES})
     set_target_properties (cvmfs_swissknife_debug PROPERTIES COMPILE_FLAGS "${CVMFS_SWISSKNIFE_CFLAGS} -DDEBUGMSG -g -O0" LINK_FLAGS "${CVMFS_SWISSKNIFE_DEBUG_LD_FLAGS}")
-    target_link_libraries (cvmfs_swissknife_debug
+    target_link_libraries (cvmfs_swissknife_debug cvmfs_util_debug
                            ${SQLITE3_LIBRARY}  ${CURL_LIBRARIES}
                            ${CARES_LIBRARIES} ${CARES_LDFLAGS} ${ZLIB_LIBRARIES}
                            ${OPENSSL_LIBRARIES} ${RT_LIBRARY} ${VJSON_LIBRARIES}
@@ -1024,13 +1024,14 @@ if (BUILD_SERVER)
     set_target_properties (cvmfs_server_debug PROPERTIES
       VERSION ${CernVM-FS_VERSION_STRING}
       COMPILE_FLAGS "${LIBCVMFS_SERVER_CFLAGS} -DDEBUGMSG -g -O0")
-    target_link_libraries (cvmfs_server_debug ${LIBCVMFS_SERVER_LIBS} ${LIBCVMFS_SERVER_LINK_LIBRARIES})
+    target_link_libraries (cvmfs_server_debug cvmfs_util_debug
+                           ${LIBCVMFS_SERVER_LIBS} ${LIBCVMFS_SERVER_LINK_LIBRARIES})
 
     add_executable (cvmfs_publish_debug ${CVMFS_PUBLISH_SOURCES})
     set_target_properties (cvmfs_publish_debug
       PROPERTIES COMPILE_FLAGS "${CVMFS_PUBLISH_CFLAGS} -DDEBUGMSG -g -O0"
       LINK_FLAGS "${CVMFS_PUBLISH_LD_FLAGS}")
-    target_link_libraries (cvmfs_publish_debug cvmfs_server_debug)
+    target_link_libraries (cvmfs_publish_debug cvmfs_util_debug cvmfs_server_debug)
   endif (BUILD_SERVER_DEBUG)
 endif (BUILD_SERVER)
 
diff --git a/cvmfs/publish/cmd_commit.cc b/cvmfs/publish/cmd_commit.cc
index 4168291e2c2cd9f093607e343b848f5bfb6a23bf..2c1acad50e82d94018fcd3759b97d20e5746a4d1 100644
--- a/cvmfs/publish/cmd_commit.cc
+++ b/cvmfs/publish/cmd_commit.cc
@@ -29,7 +29,7 @@ int CmdCommit::Main(const Options &options) {
 
   if (!options.plain_args().empty()) {
     std::vector<std::string> tokens =
-      SplitString(options.plain_args()[0].value_str, '/', 2);
+      SplitStringBounded(2, options.plain_args()[0].value_str, '/');
     fqrn = tokens[0];
   }
 
diff --git a/cvmfs/publish/cmd_transaction.cc b/cvmfs/publish/cmd_transaction.cc
index 4a6408b3e2cfbf53dcc9b7f98b92648aa819d96a..3c4700e26b077234ee4eb3a102b57cb8f95043cc 100644
--- a/cvmfs/publish/cmd_transaction.cc
+++ b/cvmfs/publish/cmd_transaction.cc
@@ -29,7 +29,7 @@ int CmdTransaction::Main(const Options &options) {
   std::string lease_path;
   if (!options.plain_args().empty()) {
     std::vector<std::string> tokens =
-      SplitString(options.plain_args()[0].value_str, '/', 2);
+      SplitStringBounded(2, options.plain_args()[0].value_str, '/');
     fqrn = tokens[0];
     if (tokens.size() == 2)
       lease_path = MakeCanonicalPath(tokens[1]);
diff --git a/cvmfs/shrinkwrap/spec_tree.cc b/cvmfs/shrinkwrap/spec_tree.cc
index c406124c1bdb8938d87c1a68dc34ce70890f71e5..65a84b1df41c873613b1cdea12428c3f592689e2 100644
--- a/cvmfs/shrinkwrap/spec_tree.cc
+++ b/cvmfs/shrinkwrap/spec_tree.cc
@@ -57,7 +57,7 @@ bool SpecTree::IsMatching(std::string path) {
   if (path.length() > 0 && path.at(path.length()-1) == '/') {
     path.erase(path.length()-1);
   }
-  std::vector<std::string> path_parts = SplitString(path, '/', 256);
+  std::vector<std::string> path_parts = SplitString(path, '/');
   for (std::vector<std::string>::const_iterator part_it = path_parts.begin()+1;
       part_it != path_parts.end();
       part_it++) {
@@ -147,9 +147,8 @@ void SpecTree::Parse(FILE *spec_file) {
     }
     char passthrough_mode = '_';
     if (inclusion_mode == '!') passthrough_mode = '-';
-    // TODO(steuber): max_chunks?
     // Split remaining path into its parts
-    std::vector<std::string> path_parts = SplitString(line, '/', 256);
+    std::vector<std::string> path_parts = SplitString(line, '/');
     cur_node = node_cache.top()->node;
     // Store second last mode to check whether it is NOT passthrough!
     char past_1_mode = node_cache.top()->node->mode;
@@ -206,7 +205,7 @@ int SpecTree::ListDir(const char *dir,
   if (path.length() > 0 && path.at(path.length()-1) == '/') {
     path.erase(path.length()-1);
   }
-  std::vector<std::string> path_parts = SplitString(path, '/', 256);
+  std::vector<std::string> path_parts = SplitString(path, '/');
   for (std::vector<std::string>::const_iterator part_it = path_parts.begin()+1;
       part_it != path_parts.end();
       part_it++) {
diff --git a/cvmfs/sync_item.cc b/cvmfs/sync_item.cc
index 88a6a17735000b8928b247ca89c39df4b2d83d40..5b4f333b1aed7937b4ab41467e63e1e4020db0f9 100644
--- a/cvmfs/sync_item.cc
+++ b/cvmfs/sync_item.cc
@@ -325,7 +325,7 @@ void SyncItem::CheckGraft() {
     if (!trimmed_line.size()) {continue;}
     if (trimmed_line[0] == '#') {continue;}
 
-    std::vector<std::string> info = SplitString(trimmed_line, '=', 2);
+    std::vector<std::string> info = SplitStringBounded(2, trimmed_line, '=');
 
     if (info.size() != 2) {
       LogCvmfs(kLogFsTraversal, kLogWarning, "Invalid line in graft file: %s",
diff --git a/cvmfs/util/CMakeLists.txt b/cvmfs/util/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..68befea444577adf72520891fedfcc3dc62c3475
--- /dev/null
+++ b/cvmfs/util/CMakeLists.txt
@@ -0,0 +1,40 @@
+if (BUILD_CVMFS OR BUILD_SERVER OR BUILD_RECEIVER OR
+    BUILD_SHRINKWRAP OR BUILD_PRELOADER OR BUILD_UNITTESTS)
+
+set (LIBCVMFS_UTIL_SOURCES
+  util/string.cc
+)
+
+set (LIBCVMFS_UTIL_CFLAGS "${CMAKE_CXX_FLAGS} \
+                          -D_FILE_OFFSET_BITS=64 \
+                          -DCVMFS_LIBRARY \
+                          -DCVMFS_RAISE_EXCEPTIONS \
+                          -fexceptions")
+set (LIBCVMFS_UTIL_LINK_LIBRARIES "")
+list (APPEND LIBCVMFS_UTIL_LINK_LIBRARIES
+      ${RT_LIBRARY}
+      pthread
+      dl)
+
+add_library(cvmfs_util SHARED ${LIBCVMFS_UTIL_SOURCES})
+set_target_properties (cvmfs_util PROPERTIES
+    VERSION ${CernVM-FS_VERSION_STRING}
+    COMPILE_FLAGS "${LIBCVMFS_UTIL_CFLAGS}")
+target_link_libraries (cvmfs_util ${LIBCVMFS_UTIL_LINK_LIBRARIES})
+
+install (TARGETS cvmfs_util LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+
+if (BUILD_SERVER_DEBUG OR BUILD_RECEIVER_DEBUG OR BUILD_UNITTESTS_DEBUG)
+
+add_library(cvmfs_util_debug SHARED ${LIBCVMFS_UTIL_SOURCES})
+set_target_properties (cvmfs_util_debug PROPERTIES
+    VERSION ${CernVM-FS_VERSION_STRING}
+    COMPILE_FLAGS "${LIBCVMFS_UTIL_CFLAGS} -DDEBUGMSG -g -O0")
+target_link_libraries (cvmfs_util_debug ${LIBCVMFS_UTIL_LINK_LIBRARIES})
+install (TARGETS cvmfs_util_debug LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+endif () # debug binaries
+
+
+endif () # components that need libcvmfs-util
diff --git a/cvmfs/util/export.h b/cvmfs/util/export.h
new file mode 100644
index 0000000000000000000000000000000000000000..c1ffe907753ff1b5f1b102ec7b6a7a76f5e5d754
--- /dev/null
+++ b/cvmfs/util/export.h
@@ -0,0 +1,14 @@
+/**
+ * This file is part of the CernVM File System.
+ */
+
+#ifndef CVMFS_UTIL_EXPORT_H_
+#define CVMFS_UTIL_EXPORT_H_
+
+#ifdef CVMFS_LIBRARY
+#define CVMFS_EXPORT __attribute__((visibility("default")))
+#else
+#define CVMFS_EXPORT
+#endif
+
+#endif  // CVMFS_UTIL_EXPORT_H_
diff --git a/cvmfs/util/string.cc b/cvmfs/util/string.cc
index 51d289d6e767760fd5db1c4b1e0514ba95a8551f..7347ec9df830934525f7bee1b559e598817b0467 100644
--- a/cvmfs/util/string.cc
+++ b/cvmfs/util/string.cc
@@ -285,8 +285,13 @@ bool HasSuffix(const std::string &str, const std::string &suffix,
              : std::equal(suffix.rbegin(), suffix.rend(), str.rbegin());
 }
 
-vector<string> SplitString(const string &str, const char delim,
-                           const unsigned max_chunks) {
+vector<string> SplitString(const string &str, char delim) {
+  return SplitStringBounded(0, str, delim);
+}
+
+vector<string> SplitStringBounded(
+  unsigned max_chunks, const string &str, char delim)
+{
   vector<string> result;
 
   // edge case... one chunk is always the whole string
diff --git a/cvmfs/util/string.h b/cvmfs/util/string.h
index 90a8bdc4f04c2e91bea541c976d07dcdd7521e03..aee175426992b21cebe66c23a2887081ebe22890 100644
--- a/cvmfs/util/string.h
+++ b/cvmfs/util/string.h
@@ -13,6 +13,8 @@
 #include <string>
 #include <vector>
 
+#include "util/export.h"
+
 const int kTrimNone = 0;
 const int kTrimLeading = 1 << 0;
 const int kTrimTrailing = 1 << 1;
@@ -22,54 +24,62 @@ const int kTrimAll = kTrimLeading | kTrimTrailing;
 namespace CVMFS_NAMESPACE_GUARD {
 #endif
 
-std::string StringifyBool(const bool value);
-std::string StringifyInt(const int64_t value);
-std::string StringifyUint(const uint64_t value);
-std::string StringifyByteAsHex(const unsigned char value);
-std::string StringifyDouble(const double value);
-std::string StringifyTime(const time_t seconds, const bool utc);
-std::string StringifyTimeval(const timeval value);
-std::string RfcTimestamp();
-std::string IsoTimestamp();
-std::string WhitelistTimestamp(time_t when);
-time_t IsoTimestamp2UtcTime(const std::string &iso8601);
-int64_t String2Int64(const std::string &value);
-uint64_t String2Uint64(const std::string &value);
-bool String2Uint64Parse(const std::string &value, uint64_t *result);
+CVMFS_EXPORT std::string StringifyBool(const bool value);
+CVMFS_EXPORT std::string StringifyInt(const int64_t value);
+CVMFS_EXPORT std::string StringifyUint(const uint64_t value);
+CVMFS_EXPORT std::string StringifyByteAsHex(const unsigned char value);
+CVMFS_EXPORT std::string StringifyDouble(const double value);
+CVMFS_EXPORT std::string StringifyTime(const time_t seconds, const bool utc);
+CVMFS_EXPORT std::string StringifyTimeval(const timeval value);
+CVMFS_EXPORT std::string RfcTimestamp();
+CVMFS_EXPORT std::string IsoTimestamp();
+CVMFS_EXPORT std::string WhitelistTimestamp(time_t when);
+CVMFS_EXPORT time_t IsoTimestamp2UtcTime(const std::string &iso8601);
+CVMFS_EXPORT int64_t String2Int64(const std::string &value);
+CVMFS_EXPORT uint64_t String2Uint64(const std::string &value);
+CVMFS_EXPORT bool String2Uint64Parse(const std::string &value,
+                                     uint64_t *result);
 
-void String2Uint64Pair(const std::string &value, uint64_t *a, uint64_t *b);
-bool HasPrefix(const std::string &str, const std::string &prefix,
-               const bool ignore_case);
-bool HasSuffix(const std::string &str, const std::string &suffix,
-               const bool ignore_case);
+CVMFS_EXPORT void String2Uint64Pair(const std::string &value,
+                                    uint64_t *a, uint64_t *b);
+CVMFS_EXPORT bool HasPrefix(const std::string &str, const std::string &prefix,
+                            const bool ignore_case);
+CVMFS_EXPORT bool HasSuffix(const std::string &str, const std::string &suffix,
+                            const bool ignore_case);
 
-std::vector<std::string> SplitString(const std::string &str, const char delim,
-                                     const unsigned max_chunks = 0);
-std::string JoinStrings(const std::vector<std::string> &strings,
-                        const std::string &joint);
-void ParseKeyvalMem(const unsigned char *buffer, const unsigned buffer_size,
-                    std::map<char, std::string> *content);
-bool ParseKeyvalPath(const std::string &filename,
-                     std::map<char, std::string> *content);
+CVMFS_EXPORT std::vector<std::string> SplitStringBounded(
+  unsigned max_chunks, const std::string &str, char delim);
+CVMFS_EXPORT std::vector<std::string> SplitString(const std::string &str,
+                                                  char delim);
 
-std::string GetLineMem(const char *text, const int text_size);
-bool GetLineFile(FILE *f, std::string *line);
-bool GetLineFd(const int fd, std::string *line);
-std::string Trim(const std::string &raw, bool trim_newline = false);
-std::string TrimString(const std::string& path,
-                       const std::string& toTrim,
-                       const int trimMode = kTrimAll);
+CVMFS_EXPORT std::string JoinStrings(const std::vector<std::string> &strings,
+                                     const std::string &joint);
+CVMFS_EXPORT void ParseKeyvalMem(const unsigned char *buffer,
+                                 const unsigned buffer_size,
+                                 std::map<char, std::string> *content);
+CVMFS_EXPORT bool ParseKeyvalPath(const std::string &filename,
+                                  std::map<char, std::string> *content);
 
-std::string ToUpper(const std::string &mixed_case);
-std::string ReplaceAll(const std::string &haystack, const std::string &needle,
-                       const std::string &replace_by);
-std::string Tail(const std::string &source, unsigned num_lines);
+CVMFS_EXPORT std::string GetLineMem(const char *text, const int text_size);
+CVMFS_EXPORT bool GetLineFile(FILE *f, std::string *line);
+CVMFS_EXPORT bool GetLineFd(const int fd, std::string *line);
+CVMFS_EXPORT std::string Trim(const std::string &raw,
+                              bool trim_newline = false);
+CVMFS_EXPORT std::string TrimString(const std::string& path,
+                                    const std::string& toTrim,
+                                    const int trimMode = kTrimAll);
 
-std::string Base64(const std::string &data);
-std::string Base64Url(const std::string &data);
-bool Debase64(const std::string &data, std::string *decoded);
-std::string GetGMTimestamp(const std::string &format = "%Y-%m-%d %H:%M:%S");
+CVMFS_EXPORT std::string ToUpper(const std::string &mixed_case);
+CVMFS_EXPORT std::string ReplaceAll(const std::string &haystack,
+                                    const std::string &needle,
+                                    const std::string &replace_by);
+CVMFS_EXPORT std::string Tail(const std::string &source, unsigned num_lines);
 
+CVMFS_EXPORT std::string Base64(const std::string &data);
+CVMFS_EXPORT std::string Base64Url(const std::string &data);
+CVMFS_EXPORT bool Debase64(const std::string &data, std::string *decoded);
+CVMFS_EXPORT std::string GetGMTimestamp(
+  const std::string &format = "%Y-%m-%d %H:%M:%S");
 
 #ifdef CVMFS_NAMESPACE_GUARD
 }  // namespace CVMFS_NAMESPACE_GUARD
diff --git a/packaging/debian/cvmfs/control.in b/packaging/debian/cvmfs/control.in
index 0552c04e08d3993b8ca904c120c0a904b1e26efa..1aaa8ad53a63203a05482146acf686fb416ad159 100644
--- a/packaging/debian/cvmfs/control.in
+++ b/packaging/debian/cvmfs/control.in
@@ -18,10 +18,19 @@ Homepage: http://cernvm.cern.ch
 Description: CernVM File System
  HTTP File System for Distributing Software to CernVM.
 
+Package: cvmfs-libs
+Architecture: i386 amd64 armhf arm64
+#Pre-Depends: ${misc:Pre-Depends}   (preparation for multiarch support)
+Depends: ${misc:Depends}
+#Multi-Arch: same   (preparation for multiarch support)
+Homepage: http://cernvm.cern.ch
+Description: CernVM-FS common libraries
+ Common utility libraries for CernVM-FS packages.
+
 Package: cvmfs-server
 Architecture: i386 amd64 armhf arm64
 #Pre-Depends: ${misc:Pre-Depends}   (preparation for multiarch support)
-Depends: psmisc, curl, attr, openssl, libcap2, libcap2-bin, lsof, rsync, jq, usbutils, sqlite3, ${misc:Depends}
+Depends: psmisc, curl, attr, openssl, libcap2, libcap2-bin, lsof, rsync, jq, usbutils, sqlite3, cvmfs-libs (= ${binary:Version}), ${misc:Depends}
 Recommends: apache2
 Conflicts: cvmfs-server (<< 2.1)
 #Multi-Arch: same   (preparation for multiarch support)
diff --git a/packaging/debian/cvmfs/cvmfs-libs.install.in b/packaging/debian/cvmfs/cvmfs-libs.install.in
new file mode 100644
index 0000000000000000000000000000000000000000..85e3bb15e7f79524312a22346f9a371f7815945d
--- /dev/null
+++ b/packaging/debian/cvmfs/cvmfs-libs.install.in
@@ -0,0 +1,4 @@
+usr/lib/libcvmfs_util.so.@CVMFS_VERSION@
+usr/lib/libcvmfs_util.so
+usr/lib/libcvmfs_util_debug.so.@CVMFS_VERSION@
+usr/lib/libcvmfs_util_debug.so
diff --git a/packaging/debian/cvmfs/rules b/packaging/debian/cvmfs/rules
index 4c28f615df9250f6c11b4e53d151eada50a06e3b..439c09923c35b7b58be13bea662c4dccbf1eba61 100755
--- a/packaging/debian/cvmfs/rules
+++ b/packaging/debian/cvmfs/rules
@@ -45,7 +45,7 @@ clean:
 debian/%.install: debian/%.install.in
 	sed "s/@CVMFS_VERSION@/$(shell dpkg-parsechangelog | sed -n -e 's/^Version: \([0-9]\.[0-9][0-9]\.[0-9][0-9]*\).*/\1/p')/g" $< > $@
 
-install: build debian/cvmfs.install debian/cvmfs-server.install debian/cvmfs-unittests.install debian/cvmfs-fuse3.install debian/cvmfs-gateway.install
+install: build debian/cvmfs-libs.install debian/cvmfs.install debian/cvmfs-server.install debian/cvmfs-unittests.install debian/cvmfs-fuse3.install debian/cvmfs-gateway.install
 	dh_testdir
 	dh_testroot
 	dh_prep
diff --git a/packaging/rpm/cvmfs-universal.spec b/packaging/rpm/cvmfs-universal.spec
index 1857967d1223716b90a0de2fb539c72e024c93d9..d9ad68a31eab9020885f8b46e47c565b6cc3ca08 100644
--- a/packaging/rpm/cvmfs-universal.spec
+++ b/packaging/rpm/cvmfs-universal.spec
@@ -191,6 +191,11 @@ HTTP File System for Distributing Software to CernVM.
 See http://cernvm.cern.ch
 Copyright (c) CERN
 
+%package libs
+Summary: CernVM-FS common libraries
+%description libs
+Common utility libraries for CernVM-FS packages
+
 %if 0%{?build_fuse3}
 %package fuse3
 Summary: additional libraries to enable libfuse3 support
@@ -251,6 +256,7 @@ Requires: jq
 Requires(post): /usr/sbin/semanage
 Requires(postun): /usr/sbin/semanage
 %endif
+Requires: cvmfs-libs = %{version}
 
 Conflicts: cvmfs-server < 2.1
 
@@ -488,6 +494,9 @@ if [ -d /var/run/cvmfs ]; then
 fi
 :
 
+%post libs
+/sbin/ldconfig
+
 %if 0%{?build_fuse3}
 %post fuse3
 /sbin/ldconfig
@@ -564,6 +573,9 @@ fi
 %endif
 /sbin/ldconfig
 
+%postun libs
+/sbin/ldconfig
+
 %if 0%{?build_gateway}
 %postun gateway
 systemctl daemon-reload
@@ -607,6 +619,14 @@ systemctl daemon-reload
 %config(noreplace) %{_sysconfdir}/bash_completion.d/cvmfs
 %doc COPYING AUTHORS README.md ChangeLog
 
+%files libs
+%defattr(-,root,root)
+%{_libdir}/libcvmfs_util.so
+%{_libdir}/libcvmfs_util.so.%{version}
+%{_libdir}/libcvmfs_util_debug.so
+%{_libdir}/libcvmfs_util_debug.so.%{version}
+%doc COPYING AUTHORS README.md ChangeLog
+
 %if 0%{?build_fuse3}
 %files fuse3
 %defattr(-,root,root)
diff --git a/test/cloud_testing/platforms/centos7_x86_64-EXCLCACHE_setup.sh b/test/cloud_testing/platforms/centos7_x86_64-EXCLCACHE_setup.sh
index cfd09261e61d6439b06184243b0d0d5d947f59b2..30ff51fbf4c8af71f8abeeac41fd5f9dd0e0a782 100755
--- a/test/cloud_testing/platforms/centos7_x86_64-EXCLCACHE_setup.sh
+++ b/test/cloud_testing/platforms/centos7_x86_64-EXCLCACHE_setup.sh
@@ -10,6 +10,7 @@ install_from_repo epel-release
 # install CernVM-FS RPM packages
 echo "installing RPM packages... "
 install_rpm "$CONFIG_PACKAGES"
+install_rpm $LIBS_PACKAGE
 install_rpm $CLIENT_PACKAGE
 install_rpm $UNITTEST_PACKAGE
 
diff --git a/test/cloud_testing/platforms/centos7_x86_64-FUSE3_setup.sh b/test/cloud_testing/platforms/centos7_x86_64-FUSE3_setup.sh
index 58a34e88caec28e1badbcbdbc406889cbab3dc0e..51800ef0f5b50694eb67963a2a1bf988ba365bd4 100755
--- a/test/cloud_testing/platforms/centos7_x86_64-FUSE3_setup.sh
+++ b/test/cloud_testing/platforms/centos7_x86_64-FUSE3_setup.sh
@@ -10,6 +10,7 @@ install_from_repo epel-release
 # install CernVM-FS RPM packages
 echo "installing RPM packages... "
 install_rpm "$CONFIG_PACKAGES"
+install_rpm $LIBS_PACKAGE
 install_rpm $CLIENT_PACKAGE
 install_rpm $UNITTEST_PACKAGE
 install_rpm $FUSE3_PACKAGE
diff --git a/test/cloud_testing/platforms/centos7_x86_64-NFS_setup.sh b/test/cloud_testing/platforms/centos7_x86_64-NFS_setup.sh
index 2b217cb51e19af9fb481a1262d21527e27a49b67..f08e39f3c5fafe53994ce7ba885236e19559d2a4 100755
--- a/test/cloud_testing/platforms/centos7_x86_64-NFS_setup.sh
+++ b/test/cloud_testing/platforms/centos7_x86_64-NFS_setup.sh
@@ -10,6 +10,7 @@ install_from_repo epel-release
 # install CernVM-FS RPM packages
 echo "installing RPM packages... "
 install_rpm "$CONFIG_PACKAGES"
+install_rpm $LIBS_PACKAGE
 install_rpm $CLIENT_PACKAGE
 install_rpm $UNITTEST_PACKAGE
 
diff --git a/test/cloud_testing/platforms/centos7_x86_64-RAMCACHE_setup.sh b/test/cloud_testing/platforms/centos7_x86_64-RAMCACHE_setup.sh
index 1c28c1d1bcb3c0840790e88a4e5f791b58472645..31c5b907ec8a356f32e25f65692f0160fdd2005a 100755
--- a/test/cloud_testing/platforms/centos7_x86_64-RAMCACHE_setup.sh
+++ b/test/cloud_testing/platforms/centos7_x86_64-RAMCACHE_setup.sh
@@ -10,6 +10,7 @@ install_from_repo epel-release
 # install CernVM-FS RPM packages
 echo "installing RPM packages... "
 install_rpm "$CONFIG_PACKAGES"
+install_rpm $LIBS_PACKAGE
 install_rpm $CLIENT_PACKAGE
 install_rpm $UNITTEST_PACKAGE
 
diff --git a/test/cloud_testing/platforms/centos7_x86_64_setup.sh b/test/cloud_testing/platforms/centos7_x86_64_setup.sh
index b99dcf2028d90f30ec16622d60522baaa4c46fcc..0643af0da9ecd616a2ca5ae61ce5ea95682f824f 100755
--- a/test/cloud_testing/platforms/centos7_x86_64_setup.sh
+++ b/test/cloud_testing/platforms/centos7_x86_64_setup.sh
@@ -23,6 +23,7 @@ install_from_repo epel-release
 # install CernVM-FS RPM packages
 echo "installing RPM packages... "
 install_rpm "$CONFIG_PACKAGES"
+install_rpm $LIBS_PACKAGE
 install_rpm $CLIENT_PACKAGE
 install_rpm $SERVER_PACKAGE
 install_rpm $DEVEL_PACKAGE
diff --git a/test/cloud_testing/platforms/centos8_x86_64-CONTAINER_setup.sh b/test/cloud_testing/platforms/centos8_x86_64-CONTAINER_setup.sh
index 2ab5c9bff8390e10cbbd69e9945ad2be00af5c44..042a65b6003123f8214542aedb8fb75c43158ef7 100644
--- a/test/cloud_testing/platforms/centos8_x86_64-CONTAINER_setup.sh
+++ b/test/cloud_testing/platforms/centos8_x86_64-CONTAINER_setup.sh
@@ -7,6 +7,7 @@ script_location=$(dirname $(readlink --canonicalize $0))
 # install CernVM-FS RPM packages
 echo "installing RPM packages... "
 install_rpm "$CONFIG_PACKAGES"
+install_rpm $LIBS_PACKAGE
 install_rpm $CLIENT_PACKAGE
 
 [ "x$SERVICE_CONTAINER" != "x" ] || die "fail (service container missing)"
diff --git a/test/cloud_testing/platforms/centos8_x86_64-FUSE3_setup.sh b/test/cloud_testing/platforms/centos8_x86_64-FUSE3_setup.sh
index 9dd2737d2c2207cd9ac48baf815ab8e6624c9c66..a01f58a8985db568d8cc5f7ad73be4ce30f6251c 100755
--- a/test/cloud_testing/platforms/centos8_x86_64-FUSE3_setup.sh
+++ b/test/cloud_testing/platforms/centos8_x86_64-FUSE3_setup.sh
@@ -10,6 +10,7 @@ install_from_repo epel-release
 # install CernVM-FS RPM packages
 echo "installing RPM packages... "
 install_rpm "$CONFIG_PACKAGES"
+install_rpm $LIBS_PACKAGE
 install_rpm $CLIENT_PACKAGE
 install_rpm $UNITTEST_PACKAGE
 install_rpm $FUSE3_PACKAGE
diff --git a/test/cloud_testing/platforms/centos8_x86_64_setup.sh b/test/cloud_testing/platforms/centos8_x86_64_setup.sh
index 9645bd0ca37dda29e921191e79cd390d85ce0af1..eaa45f5770ef506a83ff40b293abad55c0698e99 100755
--- a/test/cloud_testing/platforms/centos8_x86_64_setup.sh
+++ b/test/cloud_testing/platforms/centos8_x86_64_setup.sh
@@ -23,6 +23,7 @@ install_from_repo epel-release
 # install CernVM-FS RPM packages
 echo "installing RPM packages... "
 install_rpm "$CONFIG_PACKAGES"
+install_rpm $LIBS_PACKAGE
 install_rpm $CLIENT_PACKAGE
 install_rpm $SERVER_PACKAGE
 install_rpm $DEVEL_PACKAGE
diff --git a/test/cloud_testing/platforms/centos9_x86_64_setup.sh b/test/cloud_testing/platforms/centos9_x86_64_setup.sh
index 72d0aa9c0de72321386db059d1fcc1edb06d2ec0..98179d3edc167d5ec35eb428e00f4195aa48ff64 100755
--- a/test/cloud_testing/platforms/centos9_x86_64_setup.sh
+++ b/test/cloud_testing/platforms/centos9_x86_64_setup.sh
@@ -11,6 +11,7 @@ install_from_repo epel-release
 # install CernVM-FS RPM packages
 echo "installing RPM packages... "
 install_rpm "$CONFIG_PACKAGES"
+install_rpm $LIBS_PACKAGE
 install_rpm $CLIENT_PACKAGE
 install_rpm $SERVER_PACKAGE
 install_rpm $DEVEL_PACKAGE
diff --git a/test/cloud_testing/platforms/common_setup.sh b/test/cloud_testing/platforms/common_setup.sh
index e43fd6e384fab90a06bc853dadef448c502683c2..a3204e11eec5f5044afc784768fe4ec9d0e6540a 100644
--- a/test/cloud_testing/platforms/common_setup.sh
+++ b/test/cloud_testing/platforms/common_setup.sh
@@ -16,6 +16,7 @@ script_location=$(portable_dirname $0)
 # After sourcing this file the following variables are set:
 #
 #  SERVER_PACKAGE        location of the CernVM-FS server package to install
+#  LIBS_PACKAGE          location of the CernVM-FS library package to install
 #  CLIENT_PACKAGE        location of the CernVM-FS client package to install
 #  FUSE3_PACKAGE         location of the libcvmfs_fuse3 package
 #  DEVEL_PACKAGE         location of the CernVM-FS devel package to install
@@ -28,6 +29,7 @@ script_location=$(portable_dirname $0)
 #  DUCC_PACKAGE          location of the DUCC package
 #
 
+LIBS_PACKAGE=""
 SERVER_PACKAGE=""
 CLIENT_PACKAGE=""
 FUSE3_PACKAGE=""
@@ -62,8 +64,11 @@ usage() {
 }
 
 # parse script parameters (same for all platforms)
-while getopts "s:c:d:k:t:g:l:w:n:p:f:D:C:" option; do
+while getopts "s:c:d:k:t:g:l:w:n:p:f:D:C:L:" option; do
   case $option in
+    L)
+      LIBS_PACKAGE=$OPTARG
+      ;;
     s)
       SERVER_PACKAGE=$OPTARG
       ;;
diff --git a/test/cloud_testing/platforms/debian_setup.sh b/test/cloud_testing/platforms/debian_setup.sh
index 6d6cab21ba22b99752e7ee2a1b914d9af010c788..4f23e36f3e004574363cb3c01b540c4a72322275 100644
--- a/test/cloud_testing/platforms/debian_setup.sh
+++ b/test/cloud_testing/platforms/debian_setup.sh
@@ -62,6 +62,7 @@ echo "done"
 # install deb packages
 echo "installing DEB packages... "
 install_deb "$CONFIG_PACKAGES"
+install_deb $LIBS_PACKAGE
 install_deb $CLIENT_PACKAGE
 install_deb $FUSE3_PACKAGE
 install_deb $SERVER_PACKAGE
diff --git a/test/cloud_testing/platforms/fedora_setup.sh b/test/cloud_testing/platforms/fedora_setup.sh
index 61fcfcd572f20af9db72a0f0168887c1cf893257..e47bbf76ccc724446c2f04237eff8c09f201a333 100755
--- a/test/cloud_testing/platforms/fedora_setup.sh
+++ b/test/cloud_testing/platforms/fedora_setup.sh
@@ -17,6 +17,7 @@ echo "done"
 # install CernVM-FS RPM packages
 echo "installing RPM packages... "
 install_rpm "$CONFIG_PACKAGES"
+install_rpm $LIBS_PACKAGE
 install_rpm $CLIENT_PACKAGE
 install_rpm $SERVER_PACKAGE
 install_rpm $DEVEL_PACKAGE
diff --git a/test/cloud_testing/platforms/ubuntu_setup.sh b/test/cloud_testing/platforms/ubuntu_setup.sh
index 975da043949b66c0f8e37a73058a4c6bb6896d95..de5bb441dff720c5b5a68ce86348f97552e4a645 100755
--- a/test/cloud_testing/platforms/ubuntu_setup.sh
+++ b/test/cloud_testing/platforms/ubuntu_setup.sh
@@ -62,6 +62,7 @@ echo "done"
 # install deb packages
 echo "installing DEB packages... "
 install_deb "$CONFIG_PACKAGES"
+install_deb $LIBS_PACKAGE
 install_deb $CLIENT_PACKAGE
 install_deb $SERVER_PACKAGE
 install_deb $DEVEL_PACKAGE
diff --git a/test/cloud_testing/platforms/yubikey_setup.sh b/test/cloud_testing/platforms/yubikey_setup.sh
index c7716c1325b97f3d927527cefb1b9dde3fee2ad5..b3ade087c94f6316a4812b11f99750812322fe3c 100644
--- a/test/cloud_testing/platforms/yubikey_setup.sh
+++ b/test/cloud_testing/platforms/yubikey_setup.sh
@@ -11,6 +11,7 @@ install_from_repo epel-release
 # install CernVM-FS RPM packages
 echo "installing RPM packages... "
 install_rpm "$CONFIG_PACKAGES"
+install_rpm $LIBS_PACKAGE
 install_rpm $CLIENT_PACKAGE
 install_rpm $SERVER_PACKAGE
 install_rpm $DEVEL_PACKAGE
diff --git a/test/unittests/t_util.cc b/test/unittests/t_util.cc
index 98fe0c815e65095c54deb2b75576abd948f8e16d..b7197e1f5e5c3b8a9e9406043967a16346ef3cfb 100644
--- a/test/unittests/t_util.cc
+++ b/test/unittests/t_util.cc
@@ -1329,23 +1329,24 @@ TEST_F(T_Util, SplitString) {
   string str2 = "my::string:by:colons";
   vector<string> result;
 
-  result = SplitString(str1, ' ', 1u);
+  result = SplitStringBounded(1, str1, ' ');
   EXPECT_EQ(1u, result.size());
   EXPECT_EQ(str1, result[0]);
 
-  result = SplitString(str1, ' ', 2u);
+  result = SplitStringBounded(2, str1, ' ');
   EXPECT_EQ(2u, result.size());
   EXPECT_EQ("the", result[0]);
   EXPECT_EQ("string that will be cut in peaces", result[1]);
 
-  result = SplitString(str1, ';', 200u);
+  result = SplitStringBounded(200, str1, ';');
   EXPECT_EQ(1u, result.size());
   EXPECT_EQ(str1, result[0]);
 
-  result = SplitString(str2, ':', 200u);
+  result = SplitStringBounded(200, str2, ':');
   EXPECT_EQ(5u, result.size());
   EXPECT_EQ("", result[1]);
-  EXPECT_EQ(SplitString(str2, ':', 5u), SplitString(str2, ':', 5000u));
+  EXPECT_EQ(SplitStringBounded(5, str2, ':'),
+            SplitStringBounded(5000, str2, ':'));
 }
 
 TEST_F(T_Util, JoinStrings) {