Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • root/gnome-keyring-yubikey-unlock
1 result
Show changes
Commits on Source (2)
# gnome-keyring-yubikey-unlock
![](https://img.shields.io/badge/CXXSTD-C%2B%2B14-green)
![](https://img.shields.io/badge/CXXSTD-C%2B%2B17-green)
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
......
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)
/*
* 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>
......
/*
* 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
#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)
......