diff --git a/README.md b/README.md index b2e4a063172aad1002ea54a7d4dd4a676c494064..c5f88489f19df60a683ea035ec3d7ba5ebaf4ea8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # gnome-keyring-yubikey-unlock - + Use GnuPG to unlock gnome-keyring, which is supported by yubikey and other smartcard. @@ -15,11 +15,40 @@ Currently the only solution is to set the password of `login` keyring to empty. I encrypt the `keyring-name : password` pair with GnuPG and save it as `secret-file`. Then on starting gnome, you have yubikey inserted. Then an auto-started script call GnuPG to decrypt the secret file, and pipe use the password to unlock your keyring. GnuPG will ask you to insert yubikey. -## Dependencies +## Usage + +> I recommend you to **configure Yubikey as GPG smartcard**. The system would just ask you to unlock gnome-keyring with your default GPG software. You may generate a new GPG key for yubikey, or move your existing GPG key into yubikey. Refer to google for these knowledge. + +First, download this repo. Note the `--recursive` flag, that one's important + +``` +git clone https://github.com/recolic/gnome-keyring-yubikey-unlock --recursive +cd gnome-keyring-yubikey-unlock/src && make && cd .. +``` + +Secondly, choose an implementation: `standalone` impl only allows to unlock default keyring, and `lib` impl requires an extra library. + +<details> + <summary>Standalone Implementation</summary> + +``` +cd gnome-keyring-yubikey-unlock/src && make KEYRING_IMPL=standalone && cd .. +``` + +</details> + +<details> + <summary>Lib Implementation</summary> + +``` +cd gnome-keyring-yubikey-unlock/src && make KEYRING_IMPL=lib && cd .. +``` + +### Extra Dependency for "lib" implementation The project uses libgnome-keyring-dev -### Ubuntu 20.04 +#### Ubuntu 20.04 libgnome-keyring-dev is not in the repositories, you have to install it and its dependencies manually: @@ -37,37 +66,32 @@ sudo dpkg -i libgnome-keyring-common_3.12.0-1build1_all.deb libgnome-keyring0_3. sudo apt --fix-broken -y install ``` -### Arch Linux +#### Arch Linux ``` sudo pacman -S libgnome-keyring ``` -### Other Distro +#### Other Distro If your distribution is not providing libgnome-keyring, you can get the `.so` library from <https://archlinux.org/packages/extra/x86_64/libgnome-keyring/download>. +</details> -## Usage - -> I recommend you to **configure Yubikey as GPG smartcard**. The system would just ask you to unlock gnome-keyring with your default GPG software. You may generate a new GPG key for yubikey, or move your existing GPG key into yubikey. Refer to google for these knowledge. - -First, build the project from source. Note the `--recursive` flag, that one's important +Then, create your secret file. You may use my naive script (just in case you don't know GnuPG usage), or create an GnuPG-encrypted file by yourself. -``` -git clone https://github.com/recolic/gnome-keyring-yubikey-unlock --recursive -cd gnome-keyring-yubikey-unlock/src && make && cd .. -``` +For example, you could say `login:My_Very_Long_Login_Password`. (You may use `seahorse` or `tools/list_keyrings.sh` to determine the name of your keyring) -Then, create your secret file. +<details> + <summary>To use my naive secret file creation script</summary> ``` gnome-keyring-yubikey-unlock/create_secret_file.sh /path/to/your_secret [Your GnuPG public key] -# input your keyring:password +# input your keyring_name:password ``` -As an example, I need to input `login:My_Very_Long_Login_Password`. (You may use `seahorse` or `tools/list_keyrings.sh` to determine the name of your keyring) +</details> -Then, add the following command to gnome-autostart. If you don't know how to do it, [read me](doc/how-to-gnome-autostart.md)! +Then, add the following command to gnome-autostart. If you don't know how to do it, [read me](doc/how-to-gnome-autostart.md). ``` /path/to/this/project/unlock_keyrings.sh /path/to/your_secret diff --git a/src/Makefile b/src/Makefile index 5ee5a0790d920f0d3d0137d9841876b58db1a2c0..713cfe8a870fced7580f77c54d4fc77eddce0926 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,10 +1,14 @@ CXX ?= g++ -# Accepts CXXSTD >= C++14 -CXXFLAGS := $(shell pkg-config --cflags --libs gnome-keyring-1) -I ./lib -I . -std=c++14 -EXTRA_FLAGS ?= +CXXFLAGS := -I ./lib -I . -std=c++17 -DKEYRING_IMPL_$(KEYRING_IMPL) + +ifeq ($(KEYRING_IMPL),lib) + CXXFLAGS += $(shell pkg-config --cflags --libs gnome-keyring-1) +else ifneq ($(KEYRING_IMPL),standalone) +$(error "KEYRING_IMPL must be set to 'lib' or 'standalone'. Example: 'make KEYRING_IMPL=standalone'") +endif secret: mkdir -p ../bin/ - $(CXX) unlock_keyrings.cc -o ../bin/unlock_keyrings $(CXXFLAGS) $(EXTRA_FLAGS) + $(CXX) unlock_keyrings.cc -o ../bin/unlock_keyrings $(CXXFLAGS) diff --git a/src/keyring_op.hpp b/src/impl-libgnome-keyring.hpp similarity index 80% rename from src/keyring_op.hpp rename to src/impl-libgnome-keyring.hpp index de9d1da0c84e68c7fe2baab98a5f5a9d0efda442..f30ffddc631be1bc3363da972162ee654cf169b7 100644 --- a/src/keyring_op.hpp +++ b/src/impl-libgnome-keyring.hpp @@ -1,3 +1,10 @@ +/* + * This is an implementation to unlock gnome keyring. + * It talks to `org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface` through `/run/user/1000/bus`, + * calling the `UnlockWithMasterPassword` function, to unlock the keyring. + * It involves multiple send/recv, allows unlocking any keyring. + */ + #include <gnome-keyring-1/gnome-keyring.h> #include <string> #include <rlib/macro.hpp> diff --git a/src/impl-standalone.hpp b/src/impl-standalone.hpp new file mode 100644 index 0000000000000000000000000000000000000000..db93ec389243a7940586afabe46f0acfd142271f --- /dev/null +++ b/src/impl-standalone.hpp @@ -0,0 +1,120 @@ +/* + * This is another implementation to unlock gnome keyring. + * It sends a hand-crafted control message to `/run/user/1000/keyring/control`, to unlock the keyring. + * This interface is easier, not requiring external lib, but only allows unlocking default keyring. + * + * Credit: https://github.com/umglurf/gnome-keyring-unlock + * https://codeberg.org/umglurf/gnome-keyring-unlock + * (This repo also tells you how to unlock with TPM) + */ + +#include <rlib/macro.hpp> +#include <rlib/scope_guard.hpp> +#include <rlib/sys/sio.hpp> +#include <filesystem> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/un.h> + +namespace utils { + inline auto get_control_socket() { + namespace fs = std::filesystem; + + // Helper function to check if a path is a socket + auto is_socket = [](const fs::path& path) { + struct stat s; + return stat(path.c_str(), &s) == 0 && S_ISSOCK(s.st_mode); + }; + + if (const char* gnome_keyring_control = std::getenv("GNOME_KEYRING_CONTROL")) { + fs::path control_socket = fs::path(gnome_keyring_control) / "control"; + if (fs::exists(control_socket) && is_socket(control_socket)) { + return control_socket; + } + } + + if (const char* xdg_runtime_dir = std::getenv("XDG_RUNTIME_DIR")) { + fs::path control_socket = fs::path(xdg_runtime_dir) / "keyring/control"; + if (fs::exists(control_socket) && is_socket(control_socket)) { + return control_socket; + } + } + + throw std::runtime_error("Unable to find control socket"); + } + + inline int connect_unix_socket(std::string path) { + // Create a UNIX socket + int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd == -1) { + throw std::runtime_error("Unable to create unix socket"); + } + + // Set up socket address + struct sockaddr_un addr {}; + addr.sun_family = AF_UNIX; + std::strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1); + + // Connect to the socket + if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == -1) { + close(sockfd); + throw std::runtime_error("Unable to connect to unix socket"); + } + return sockfd; + } + + enum ControlOp : uint32_t { + INITIALIZE = 0, + UNLOCK = 1, + CHANGE = 2, + QUIT = 4 + }; + + enum ControlRes : uint32_t { + OK = 0, + DENIED = 1, + FAILED = 2, + NO_DAEMON = 3 + }; +} + +constexpr auto GNOME_KEYRING_RESULT_OK = utils::ControlRes::OK; + +inline auto do_unlock(std::string keyring, std::string password) { + auto sockfd = utils::connect_unix_socket(utils::get_control_socket()); + rlib_defer([&] { close(sockfd); }); + + rlib::sockIO::quick_send(sockfd, std::string(1, '\0')); + + uint32_t oplen = 8 + 4 + password.size(); + uint32_t pktBuf = htonl(oplen); + rlib::sockIO::sendn_ex(sockfd, &pktBuf, sizeof(pktBuf), 0); + + pktBuf = htonl(utils::ControlOp::UNLOCK); + rlib::sockIO::sendn_ex(sockfd, &pktBuf, sizeof(pktBuf), 0); + + pktBuf = htonl(password.size()); + rlib::sockIO::sendn_ex(sockfd, &pktBuf, sizeof(pktBuf), 0); + rlib::sockIO::quick_send(sockfd, password); + + rlib::sockIO::recvn_ex(sockfd, &pktBuf, sizeof(pktBuf)); + if (ntohl(pktBuf) != 8) + throw std::runtime_error("invalid api response length: expecting len = 8"); + + rlib::sockIO::recvn_ex(sockfd, &pktBuf, sizeof(pktBuf)); + return ntohl(pktBuf); +} + +inline std::string keyringResultToString(int res) { + switch (res) { +#define RLIB_IMPL_GEN_RESULT(value) RLIB_IMPL_GEN_RESULT_1(value, RLIB_MACRO_TO_CSTR(value)) +#define RLIB_IMPL_GEN_RESULT_1(value, cstr) case (utils::ControlRes::value): return (cstr) + + RLIB_IMPL_GEN_RESULT(OK); + RLIB_IMPL_GEN_RESULT(DENIED); + RLIB_IMPL_GEN_RESULT(FAILED); + RLIB_IMPL_GEN_RESULT(NO_DAEMON); + default: + return std::string("Unknown Result Code: ") + std::to_string(res); + } +} \ No newline at end of file diff --git a/src/unlock_keyrings.cc b/src/unlock_keyrings.cc index 46eb59e96e52b7e0d943548722ff9cfeea95997c..91b1f29d5464b1c6163cda5fc5eaa7636c5296b7 100644 --- a/src/unlock_keyrings.cc +++ b/src/unlock_keyrings.cc @@ -1,6 +1,11 @@ #include <rlib/log.hpp> #include <rlib/opt.hpp> -#include "keyring_op.hpp" +#ifdef KEYRING_IMPL_lib +#include "impl-libgnome-keyring.hpp" +#endif +#ifdef KEYRING_IMPL_standalone +#include "impl-standalone.hpp" +#endif rlib::logger rlog(std::cerr); @@ -39,6 +44,11 @@ int main(int argc, char **argv) { return 3; } +#ifdef KEYRING_IMPL_standalone + rlog.warning("This implementation 'standalone' always unlocks your default keyring. Keyring name `{}` will be ignored. Build with KEYRING_IMPL=lib if necessary.", keyring_and_pswd.at(0)); + keyring_and_pswd.at(0) = "_ignored_"; +#endif + auto res = do_unlock(keyring_and_pswd.at(0), keyring_and_pswd.at(1)); auto msg = keyringResultToString(res); if(res == GNOME_KEYRING_RESULT_OK)