keyring/0000755000176200001440000000000014151113235011716 5ustar liggesuserskeyring/NAMESPACE0000644000176200001440000000141714150763270013151 0ustar liggesusers# Generated by roxygen2: do not edit by hand export(backend) export(backend_env) export(backend_file) export(backend_keyrings) export(backend_macos) export(backend_secret_service) export(backend_wincred) export(default_backend) export(has_keyring_support) export(key_delete) export(key_get) export(key_get_raw) export(key_list) export(key_set) export(key_set_with_raw_value) export(key_set_with_value) export(keyring_create) export(keyring_delete) export(keyring_is_locked) export(keyring_list) export(keyring_lock) export(keyring_unlock) importFrom(R6,R6Class) importFrom(assertthat,"on_failure<-") importFrom(assertthat,assert_that) importFrom(assertthat,has_name) importFrom(utils,URLdecode) importFrom(utils,head) importFrom(utils,tail) useDynLib(keyring, .registration = TRUE) keyring/LICENSE0000644000176200001440000000004514146771774012750 0ustar liggesusersYEAR: 2017 COPYRIGHT HOLDER: RStudio keyring/README.md0000644000176200001440000000733614150747225013221 0ustar liggesusers # keyring > Access the System Credential Store from R [![R build status](https://github.com/r-lib/keyring/workflows/R-CMD-check/badge.svg)](https://github.com/r-lib/keyring/actions) [![](https://www.r-pkg.org/badges/version/keyring)](https://www.r-pkg.org/pkg/keyring) [![CRAN RStudio mirror downloads](https://cranlogs.r-pkg.org/badges/keyring)](https://www.r-pkg.org/pkg/keyring) [![Coverage Status](https://img.shields.io/codecov/c/github/r-lib/keyring/main.svg)](https://codecov.io/github/r-lib/keyring?branch=main) Platform independent API to access the operating systems credential store. Currently supports: - Keychain on macOS (`backend_macos`), - Credential Store on Windows (`backend_wincred`), - the Secret Service API on Linux (`backend_secret_service`), - encrypted files (`backend_file`), and - environment variables (`backend_env`). The last two are available on all platforms. Additional storage backends can be added easily. ## Installation ### Linux For the Secret Service backend install the `libsecret` library, at least version 0.16. This is typically the best keyring for a Linux desktop. Install these packages: - Debian/Ubuntu: `libsecret-1-dev` - Recent RedHat, Fedora and CentOS systems: `libsecret-devel` The file backend uses the sodium package, which needs the sodium library. This backend works best on Linux servers. Install these packages: - Debian/Ubuntu: `libsodium-dev` - Fedora, EPEL: `libsodium-devel` ### OS X and Windows No additional software is needed. ### R package Install the package from CRAN: ``` r install.packages("keyring") ``` ## Usage ### Query secret keys in a keyring: Each keyring can contain one or many secrets (keys). A key is defined by a service name and a password. Once a key is defined, it persists in the keyring store of the operating system. This means the keys persist beyond the termination of and R session. Specifically, you can define a key once, and then read the key value in completely independent R sessions. - Setting a secret interactively: `key_set()` - Setting a secret from a script, i.e. non-interactively: `key_set_with_value()` - Reading a secret: `key_get()` - Listing secrets: `key_list()` - Deleting a secret: `key_delete()` ### Configure an OS-specific backend: - The default is operating system specific, and is described in manual page of `default_backend()`. In most cases you don’t have to configure this. - MacOS: `backend_macos`. - Linux: `backend_secret_service`, if build with `libsecret`. - Windows: `backend_wincred` - Or store the secrets in encrypted files: `backend_file`. Should you need to change the default backend, set the `R_KEYRING_BACKEND` environment variable or the `keyring_backend` R option to the backend’s name (e.g. `env`, `file`, etc.). ### Manage keyrings: A keyring is a collection of keys that can be treated as a unit. A keyring typically has a name and a password to unlock it. See `keyring_create()`, `keyring_delete()`, `keyring_list()`, `keyring_lock()`, `keyring_unlock()`, `keyring_is_locked()`. Note that all platforms have a default keyring, and `key_get()`, etc. will use that automatically. The default keyring is also convenient, because usually the OS unlocks it automatically when you log in, so secrets are available immediately. But note that the file backend does not currently unlock its default keyring. You only need to explicitly deal with keyrings and the `keyring_*` functions if you want to use a different keyring. ## Development documentation Please see our [writeup of some `keyring` internals](https://github.com/r-lib/keyring/blob/main/inst/development-notes.md), and as always, use the source code. ## License MIT © RStudio keyring/man/0000755000176200001440000000000014150747225012504 5ustar liggesuserskeyring/man/backend_wincred.Rd0000644000176200001440000000204514146771774016111 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-wincred.R \name{backend_wincred} \alias{backend_wincred} \title{Windows Credential Store keyring backend} \description{ This backend is the default on Windows. It uses the native Windows Credential API, and needs at least Windows XP to run. } \details{ This backend supports multiple keyrings. Note that multiple keyrings are implemented in the \code{keyring} R package, using some dummy keyring keys that represent keyrings and their locked/unlocked state. See \link{backend} for the documentation of the individual methods. } \examples{ \dontrun{ ## This only works on Windows kb <- backend_wincred$new() kb$keyring_create("foobar") kb$set_default_keyring("foobar") kb$set_with_value("service", password = "secret") kb$get("service") kb$delete("service") kb$delete_keyring("foobar") } } \seealso{ Other keyring backends: \code{\link{backend_env}}, \code{\link{backend_file}}, \code{\link{backend_macos}}, \code{\link{backend_secret_service}} } \concept{keyring backends} keyring/man/backend_macos.Rd0000644000176200001440000000150114146771774015554 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-macos.R \name{backend_macos} \alias{backend_macos} \title{macOS Keychain keyring backend} \description{ This backend is the default on macOS. It uses the macOS native Keychain Service API. } \details{ It supports multiple keyrings. See \link{backend} for the documentation of the individual methods. } \examples{ \dontrun{ ## This only works on macOS kb <- backend_macos$new() kb$keyring_create("foobar") kb$set_default_keyring("foobar") kb$set_with_value("service", password = "secret") kb$get("service") kb$delete("service") kb$delete_keyring("foobar") } } \seealso{ Other keyring backends: \code{\link{backend_env}}, \code{\link{backend_file}}, \code{\link{backend_secret_service}}, \code{\link{backend_wincred}} } \concept{keyring backends} keyring/man/b_wincred_set_with_raw_value.Rd0000644000176200001440000000225314146771774020717 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-wincred.R \name{b_wincred_set_with_raw_value} \alias{b_wincred_set_with_raw_value} \title{Set a key on a Wincred keyring} \usage{ b_wincred_set_with_raw_value( self, private, service, username, password, keyring ) } \arguments{ \item{service}{Service name. Must not be empty.} \item{username}{Username. Might be empty.} \item{password}{The key text to store. \enumerate{ \item Check if we are using the default keyring. \item If yes, then just store the key and we are done. \item Otherwise check if keyring exists. \item If not, error and finish. \item If yes, check if it is locked. \item If yes, unlock it. \item Encrypt the key with the AES key, and store it. } If required, an encoding can be specified using either an R option (\code{keyring.encoding_windows}) or environment variable (\code{KEYRING_ENCODING_WINDOWS}). To set, use one of: \code{options(keyring.encoding_windows = 'encoding-type')} \code{Sys.setenv("KEYRING_ENCODING_WINDOWS" = 'encoding-type')} For a list of valid encodings, use \code{iconvlist()}} } \description{ Set a key on a Wincred keyring } \keyword{internal} keyring/man/backend_env.Rd0000644000176200001440000000243114146771774015245 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-env.R \name{backend_env} \alias{backend_env} \title{Environment variable keyring backend} \description{ This is a simple keyring backend, that stores/uses secrets in environment variables of the R session. } \details{ It does not support multiple keyrings. It also does not support listing all keys, since there is no way to distinguish keys from regular environment variables. It does support service names and usernames: they will be separated with a \code{:} character in the name of the environment variable. (Note that such an environment variable typically cannot be set or queried from a shell, but it can be set and queried from R or other programming languages.) See \link{backend} for the documentation of the class's methods. } \examples{ \dontrun{ env <- backend_env$new() env$set("r-keyring-test", username = "donaldduck") env$get("r-keyring-test", username = "donaldduck") Sys.getenv("r-keyring-test:donaldduck") # This is an error env$list() # Clean up env$delete("r-keyring-test", username = "donaldduck") } } \seealso{ Other keyring backends: \code{\link{backend_file}}, \code{\link{backend_macos}}, \code{\link{backend_secret_service}}, \code{\link{backend_wincred}} } \concept{keyring backends} keyring/man/b_wincred_decode.Rd0000644000176200001440000000145414146771774016251 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-wincred.R \name{b_wincred_decode} \alias{b_wincred_decode} \title{Decode a raw password obtained by b_wincred_get_raw} \usage{ b_wincred_decode(password, encoding = "auto") } \arguments{ \item{password}{A raw byte string returned from \code{b_wincred_get_raw}.} \item{encoding}{A character value, specifying an encoding to use. Defaults to 'auto', which will decode either of UTF-8 or UTF-16LE.} } \value{ A character value containing a password. } \description{ Defaults to 'auto' encoding, which uses \code{b_wincred_decode_auto} to accomplish the decoding (this works with decoding either UTF-8 or UTF-16LE encodings). In the case where an encoding is specified, use that to convert the raw password. } \keyword{internal} keyring/man/backend_file.Rd0000644000176200001440000000115214146771774015373 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-file.R \name{backend_file} \alias{backend_file} \title{Encrypted file keyring backend} \description{ This is a simple keyring backend, that stores/uses secrets in encrypted files. } \details{ It supports multiple keyrings. See \link{backend} for the documentation of the individual methods. } \examples{ \dontrun{ kb <- backend_file$new() } } \seealso{ Other keyring backends: \code{\link{backend_env}}, \code{\link{backend_macos}}, \code{\link{backend_secret_service}}, \code{\link{backend_wincred}} } \concept{keyring backends} keyring/man/backend_keyrings.Rd0000644000176200001440000000357114146771774016316 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-class.R \name{backend_keyrings} \alias{backend_keyrings} \title{Abstract class of a backend that supports multiple keyrings} \description{ To implement a new keyring that supports multiple keyrings, you need to inherit from this class and redefine the \code{get}, \code{set}, \code{set_with_value}, \code{delete}, \code{list} methods, and also the keyring management methods: \code{keyring_create}, \code{keyring_list}, \code{keyring_delete}, \code{keyring_lock}, \code{keyring_unlock}, \code{keyring_is_locked}, \code{keyring_default} and \code{keyring_set_default}. } \details{ See \link{backend} for the first set of methods. This is the semantics of the keyring management methods:\preformatted{keyring_create(keyring) keyring_list() keyring_delete(keyring = NULL) keyring_lock(keyring = NULL) keyring_unlock(keyring = NULL, password = NULL) keyring_is_locked(keyring = NULL) keyring_default() keyring_set_default(keyring = NULL) } \itemize{ \item \code{keyring_create()} creates a new keyring. \item \code{keyring_list()} lists all keyrings. \item \code{keyring_delete()} deletes a keyring. It is a good idea to protect the default keyring, and/or a non-empty keyring with a password or a confirmation dialog. \item \code{keyring_lock()} locks a keyring. \item \code{keyring_unlock()} unlocks a keyring. \item \code{keyring_is_locked()} checks whether a keyring is locked. \item \code{keyring_default()} returns the default keyring. \item \code{keyring_set_default()} sets the default keyring. } Arguments: \itemize{ \item \code{keyring} is the name of the keyring to use or create. For some methods in can be \code{NULL} to select the default keyring. \item \code{password} is the password of the keyring. } } \seealso{ Other keyring backend base classes: \code{\link{backend}} } \concept{keyring backend base classes} keyring/man/backend.Rd0000644000176200001440000000442014150747225014362 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-class.R \name{backend} \alias{backend} \title{Abstract class of a minimal keyring backend} \description{ To implement a new keyring backend, you need to inherit from this class and then redefine the \code{get}, \code{set}, \code{set_with_value} and \code{delete} methods. Implementing the \code{list} method is optional. Additional methods can be defined as well. } \details{ These are the semantics of the various methods:\preformatted{get(service, username = NULL, keyring = NULL) get_raw(service, username = NULL, keyring = NULL) set(service, username = NULL, keyring = NULL, prompt = "Password: ") set_with_value(service, username = NULL, password = NULL, keyring = NULL) set_with_raw_value(service, username = NULL, password = NULL, keyring = NULL) delete(service, username = NULL, keyring = NULL) list(service = NULL, keyring = NULL) } What these functions do: \itemize{ \item \code{get()} queries the secret in a keyring item. \item \code{get_raw()} is similar to \code{get()}, but returns the result as a raw vector. \item \code{set()} sets the secret in a keyring item. The secret itself is read in interactively from the keyboard. \item \code{set_with_value()} sets the secret in a keyring item to the specified value. \item \code{set_with_raw_value()} sets the secret in keyring item to the byte sequence of a raw vector. \item \code{delete()} remotes a keyring item. \item \code{list()} lists keyring items. } The arguments: \itemize{ \item \code{service} String, the name of a service. This is used to find the secret later. \item \code{username} String, the username associated with a secret. It can be \code{NULL}, if no username belongs to the secret. It uses the value of the \code{keyring_username}, if set. \item \code{keyring} String, the name of the keyring to work with. This only makes sense if the platform supports multiple keyrings. \code{NULL} selects the default (and maybe only) keyring. \item \code{password} The value of the secret, typically a password, or other credential. \item \code{prompt} String, the text to be displayed above the textbox. } } \seealso{ Other keyring backend base classes: \code{\link{backend_keyrings}} } \concept{keyring backend base classes} keyring/man/backends.Rd0000644000176200001440000000435214146771774014564 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/default_backend.R \name{backends} \alias{backends} \alias{default_backend} \title{Select the default backend and default keyring} \usage{ default_backend(keyring = NULL) } \arguments{ \item{keyring}{Character string, the name of the keyring to use, or \code{NULL} for the default keyring.} } \value{ The backend object itself. } \description{ The default backend is selected \enumerate{ \item based on the \code{keyring_backend} option. See \code{\link[base:options]{base::options()}}. This can be set to a character string, and then the \emph{backend_}\code{string} class is used to create the default backend. \item If this is not set, then the \code{R_KEYRING_BACKEND} environment variable is checked. \item If this is not set, either, then the backend is selected automatically, based on the OS: \enumerate{ \item On Windows, the Windows Credential Store (\code{"wincred"}) is used. \item On macOS, Keychain services are selected (\code{"macos"}). \item Linux uses the Secret Service API (\code{"secret_service"}), and it also checks that the service is available. It is typically only available on systems with a GUI. \item If the file backend (\code{"file"}) is available, it is selected. \item On other operating systems, secrets are stored in environment variables (\code{"env"}). } } } \details{ Most backends support multiple keyrings. For these the keyring is selected from: \enumerate{ \item the supplied \code{keyring} argument (if not \code{NULL}), or \item the \code{keyring_keyring} option. \itemize{ \item You can change this by using \code{options(keyring_keyring = "NEWVALUE")} } \item If this is not set, the \code{R_KEYRING_KEYRING} environment variable. \itemize{ \item Change this value with \code{Sys.setenv(R_KEYRING_KEYRING = "NEWVALUE")}, either in your script or in your \code{.Renviron} file. See \link[base:Startup]{base::Startup} for information about using \code{.Renviron} } \item Finally, if neither of these are set, the OS default keyring is used. \itemize{ \item Usually the keyring is automatically unlocked when the user logs in. } } } \seealso{ \link{backend_env}, \link{backend_file}, \link{backend_macos}, \link{backend_secret_service}, \link{backend_wincred} } keyring/man/b_wincred_get.Rd0000644000176200001440000000166114146771774015605 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-wincred.R \name{b_wincred_get} \alias{b_wincred_get} \title{Get a key from a Wincred keyring} \usage{ b_wincred_get(self, private, service, username, keyring) } \arguments{ \item{service}{Service name. Must not be empty.} \item{username}{Username. Might be empty. \enumerate{ \item We check if the key is on the default keyring. \item If yes, we just return it. \item Otherwise check if the keyring is locked. \item If locked, then unlock it. \item Get the AES key from the keyring. \item Decrypt the key with the AES key. } Additionally, users may specify an encoding to use when converting the password from a byte-string, for compatibility with other software such as python's keyring package. This is done via an option, or an environment variable.} \item{backend}{Backend object.} } \description{ Get a key from a Wincred keyring } \keyword{internal} keyring/man/keyring-package.Rd0000644000176200001440000000553214146771774016054 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/package.R \docType{package} \name{keyring-package} \alias{keyring} \alias{keyring-package} \title{About the keyring package} \description{ Platform independent API to many system credential store implementations. Currently supported: \itemize{ \item Keychain on macOS, \item Credential Store on Windows, \item the Secret Service API on Linux, and \item environment variables on other platforms. } } \section{Configuring an OS-specific backend}{ \itemize{ \item The default is operating system specific, and is described in \code{\link[=default_backend]{default_backend()}}. In most cases you don't have to configure this. \item MacOS: \link{backend_macos} \item Linux: \link{backend_secret_service} \item Windows: \link{backend_wincred} \item Or store the secrets in environment variables on other operating systems: \link{backend_env} } } \section{Query secret keys in a keyring}{ Each keyring can contain one or many secrets (keys). A key is defined by a service name and a password. Once a key is defined, it persists in the keyring store of the operating system. This means the keys persist beyond the termination of and R session. Specifically, you can define a key once, and then read the key value in completely independent R sessions. \itemize{ \item Setting a secret interactively: \code{\link[=key_set]{key_set()}} \item Setting a secret from a script, i.e. non-interactively: \code{\link[=key_set_with_value]{key_set_with_value()}} \item Reading a secret: \code{\link[=key_get]{key_get()}} \item Listing secrets: \code{\link[=key_list]{key_list()}} \item Deleting a secret: \code{\link[=key_delete]{key_delete()}} } } \section{Managing keyrings}{ A keyring is a collection of keys that can be treated as a unit. A keyring typically has a name and a password to unlock it. \itemize{ \item \code{\link[=keyring_create]{keyring_create()}} \item \code{\link[=keyring_delete]{keyring_delete()}} \item \code{\link[=keyring_list]{keyring_list()}} \item \code{\link[=keyring_lock]{keyring_lock()}} \item \code{\link[=keyring_unlock]{keyring_unlock()}} } Note that all platforms have a default keyring, and \code{key_get()}, etc. will use that automatically. The default keyring is also convenient, because the OS unlocks it automatically when you log in, so secrets are available immediately. You only need to explicitly deal with keyrings and the \verb{keyring_*} functions if you want to use a different keyring. } \seealso{ Useful links: \itemize{ \item \url{https://r-lib.github.io/keyring/index.html} \item \url{https://github.com/r-lib/keyring#readme} \item Report bugs at \url{https://github.com/r-lib/keyring/issues} } } \author{ \strong{Maintainer}: Gábor Csárdi \email{csardi.gabor@gmail.com} Other contributors: \itemize{ \item Alec Wong [contributor] \item RStudio [copyright holder] } } keyring/man/has_keyring_support.Rd0000644000176200001440000000565114150761360017075 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/api.R \name{has_keyring_support} \alias{has_keyring_support} \alias{keyring_create} \alias{keyring_list} \alias{keyring_delete} \alias{keyring_lock} \alias{keyring_unlock} \alias{keyring_is_locked} \title{Operations on keyrings} \usage{ has_keyring_support() keyring_create(keyring, password = NULL) keyring_list() keyring_delete(keyring = NULL) keyring_lock(keyring = NULL) keyring_unlock(keyring = NULL, password = NULL) keyring_is_locked(keyring = NULL) } \arguments{ \item{keyring}{The name of the keyring to create or to operate on. For functions other than \code{keyring_create}, it can also be \code{NULL} to select the default keyring.} \item{password}{The initial password or the password to unlock the keyring. If not specified or \code{NULL}, it will be read from the console.} } \description{ On most platforms \code{keyring} supports multiple keyrings. This includes Windows, macOS and Linux (Secret Service) as well. A keyring is a collection of keys that can be treated as a unit. A keyring typically has a name and a password to unlock it. Once a keyring is unlocked, it remains unlocked until the end of the user session, or until it is explicitly locked again. } \details{ Platforms typically have a default keyring, which is unlocked automatically when the user logs in. This keyring does not need to be unlocked explicitly. You can configure the keyring to use via R options or environment variables (see \code{\link[=default_backend]{default_backend()}}), or you can also specify it directly in the \code{\link[=default_backend]{default_backend()}} call, or in the individual \code{keyring} calls. \code{has_keyring_support} checks if a backend supports multiple keyrings. \code{keyring_create} creates a new keyring. It asks for a password if no password is specified. \code{keyring_list} lists all existing keyrings. \code{keyring_delete} deletes a keyring. Deleting a non-empty keyring requires confirmation, and the default keyring can only be deleted if specified explicitly. On some backends (e.g. Windows Credential Store), the default keyring cannot be deleted at all. \code{keyring_lock} locks a keyring. On some backends (e.g. Windows Credential Store), the default keyring cannot be locked. \code{keyring_unlock} unlocks a keyring. If a password is not specified, it will be read in interactively. \code{keyring_is_locked} queries whether a keyring is locked. } \examples{ default_backend() has_keyring_support() backend_env$new()$has_keyring_support() ## This might ask for a password, so we do not run it by default ## It only works if the default backend supports multiple keyrings \dontrun{ keyring_create("foobar") key_set_with_value("R-test-service", "donaldduck", password = "secret", keyring = "foobar") key_get("R-test-service", "donaldduck", keyring = "foobar") key_list(keyring = "foobar") keyring_delete(keyring = "foobar") } } keyring/man/backend_secret_service.Rd0000644000176200001440000000256214146771774017467 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-secret-service.R \name{backend_secret_service} \alias{backend_secret_service} \title{Linux Secret Service keyring backend} \description{ This backend is the default on Linux. It uses the libsecret library, and needs a secret service daemon running (e.g. Gnome Keyring, or KWallet). It uses DBUS to communicate with the secret service daemon. } \details{ This backend supports multiple keyrings. See \link{backend} for the documentation of the individual methods. The \code{is_available()} method checks is a Secret Service daemon is running on the system, by trying to connect to it. It returns a logical scalar, or throws an error, depending on its argument:\preformatted{is_available = function(report_error = FALSE) } Argument: \itemize{ \item \code{report_error} Whether to throw an error if the Secret Service is not available. } } \examples{ \dontrun{ ## This only works on Linux, typically desktop Linux kb <- backend_secret_service$new() kb$keyring_create("foobar") kb$set_default_keyring("foobar") kb$set_with_value("service", password = "secret") kb$get("service") kb$delete("service") kb$delete_keyring("foobar") } } \seealso{ Other keyring backends: \code{\link{backend_env}}, \code{\link{backend_file}}, \code{\link{backend_macos}}, \code{\link{backend_wincred}} } \concept{keyring backends} keyring/man/b_wincred_decode_auto.Rd0000644000176200001440000000071714146771774017302 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/backend-wincred.R \name{b_wincred_decode_auto} \alias{b_wincred_decode_auto} \title{Decode a raw password obtained by b_wincred_get_raw (UTF-8 and UTF-16LE only)} \usage{ b_wincred_decode_auto(password) } \arguments{ \item{password}{Raw vector coming from the keyring.} } \description{ It attempts to use UTF-16LE conversion if there are 0 values in the password. } \keyword{internal} keyring/man/key_get.Rd0000644000176200001440000001104514150747225014423 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/api.R \name{key_get} \alias{key_get} \alias{key_get_raw} \alias{key_set} \alias{key_set_with_value} \alias{key_set_with_raw_value} \alias{key_delete} \alias{key_list} \title{Operations on keys} \usage{ key_get(service, username = NULL, keyring = NULL) key_get_raw(service, username = NULL, keyring = NULL) key_set(service, username = NULL, keyring = NULL, prompt = "Password: ") key_set_with_value(service, username = NULL, password = NULL, keyring = NULL) key_set_with_raw_value( service, username = NULL, password = NULL, keyring = NULL ) key_delete(service, username = NULL, keyring = NULL) key_list(service = NULL, keyring = NULL) } \arguments{ \item{service}{Service name, a character scalar.} \item{username}{Username, a character scalar, or \code{NULL} if the key is not associated with a username.} \item{keyring}{For systems that support multiple keyrings, specify the name of the keyring to use here. If \code{NULL}, then the default keyring is used. See also \code{\link[=has_keyring_support]{has_keyring_support()}}.} \item{prompt}{The character string displayed when requesting the secret} \item{password}{The secret to store. For \code{key_set}, it is read from the console, interactively. \code{key_set_with_value} can be also used in non-interactive mode.} } \value{ \code{key_get} returns a character scalar, the password or other confidential information that was stored in the key. \code{key_list} returns a list of keys, i.e. service names and usernames, in a data frame. } \description{ These functions manipulate keys in a keyring. You can think of a keyring as a secure key-value store. } \details{ \code{key_get} queries a key from the keyring. \code{key_get_raw} queries a key and returns it as a raw vector. Most credential stores allow storing a byte sequence with embedded null bytes, and these cannot be represented as traditional null bytes terminated strings. If you don't know whether the key contains an embedded null, it is best to query it with \code{key_get_raw} instead of \code{key_get}. \code{key_set} sets a key in the keyring. The contents of the key is read interactively from the terminal. \code{key_set_with_value} is the non-interactive pair of \code{key_set}, to set a key in the keyring. \code{key_set_raw_with_value} sets a key to a byte sequence from a raw vector. \code{key_delete} deletes a key. \code{key_list} lists all keys of a keyring, or the keys for a certain service (if \code{service} is not \code{NULL}). \subsection{Encodings}{ On Windows, if required, an encoding can be specified using either an R option (\code{keyring.encoding_windows}) or environment variable (\code{KEYRING_ENCODING_WINDOWS}). This will be applied when both getting and setting keys. The option takes precedence over the environment variable, if both are set. This is reserved primarily for compatibility with keys set with other software, such as Python's implementation of keyring. For a list of encodings, use \code{\link[=iconvlist]{iconvlist()}}, although it should be noted that not \emph{every} encoding can be properly converted, even for trivial cases. For best results, use UTF-8 if you can. } } \examples{ # These examples use the default keyring, and they are interactive, # so, we don't run them by default \dontrun{ key_set("R-keyring-test-service", "donaldduck") key_get("R-keyring-test-service", "donaldduck") if (has_keyring_support()) key_list(service = "R-keyring-test-service") key_delete("R-keyring-test-service", "donaldduck") ## This is non-interactive, assuming that that default keyring ## is unlocked key_set_with_value("R-keyring-test-service", "donaldduck", password = "secret") key_get("R-keyring-test-service", "donaldduck") if (has_keyring_support()) key_list(service = "R-keyring-test-service") key_delete("R-keyring-test-service", "donaldduck") ## This is interactive using backend_file ## Set variables to be used in keyring kr_name <- "my_keyring" kr_service <- "my_database" kr_username <- "my_username" ## Create a keyring and add an entry using the variables above kb <- keyring::backend_file$new() ## Prompt for the keyring password, used to unlock keyring kb$keyring_create(kr_name) ## Prompt for the secret/password to be stored in the keyring kb$set(kr_service, username=kr_username, keyring=kr_name) # Lock the keyring kb$keyring_lock(kr_name) ## The keyring file is stored at ~/.config/r-keyring/ on Linux ## Output the stored password keyring::backend_file$new()$get(service = kr_service, user = kr_username, keyring = kr_name) } } keyring/DESCRIPTION0000644000176200001440000000312614151113235013426 0ustar liggesusersPackage: keyring Title: Access the System Credential Store from R Version: 1.3.0 Authors@R: c(person(given = "Gábor", family = "Csárdi", role = c("aut", "cre"), email = "csardi.gabor@gmail.com"), person(given = "Alec", family = "Wong", role = "ctb"), person("RStudio", role = "cph")) Description: Platform independent 'API' to access the operating system's credential store. Currently supports: 'Keychain' on 'macOS', Credential Store on 'Windows', the Secret Service 'API' on 'Linux', and a simple, platform independent store implemented with environment variables. Additional storage back-ends can be added easily. License: MIT + file LICENSE URL: https://r-lib.github.io/keyring/index.html, https://github.com/r-lib/keyring#readme BugReports: https://github.com/r-lib/keyring/issues RoxygenNote: 7.1.2 Imports: assertthat, askpass, openssl, R6, utils, sodium, yaml, filelock, rappdirs, tools Suggests: callr, covr, mockery, testthat, withr Encoding: UTF-8 SystemRequirements: Optional: libsecret on Linux (libsecret-1-dev on Debian/Ubuntu, libsecret-devel on Fedora/CentOS) Collate: 'api.R' 'assertions.R' 'backend-class.R' 'backend-env.R' 'backend-file.R' 'backend-macos.R' 'backend-secret-service.R' 'backend-wincred.R' 'default_backend.R' 'package.R' 'pass.R' 'utils.R' NeedsCompilation: yes Packaged: 2021-11-28 21:12:16 UTC; gaborcsardi Author: Gábor Csárdi [aut, cre], Alec Wong [ctb], RStudio [cph] Maintainer: Gábor Csárdi Repository: CRAN Date/Publication: 2021-11-29 09:00:13 UTC keyring/tests/0000755000176200001440000000000014146771774013106 5ustar liggesuserskeyring/tests/testthat/0000755000176200001440000000000014151113235014720 5ustar liggesuserskeyring/tests/testthat/test-wincred.R0000644000176200001440000000600514146771774017502 0ustar liggesusers context("Windows credential store") test_that("low level API", { skip_if_not_win() skip_on_cran() keyring <- random_keyring() service <- random_service() username <- random_username() password <- random_password() target <- b_wincred_target(keyring, service, username) expect_false(b_wincred_i_exists(target)) expect_silent(b_wincred_i_set(target, charToRaw(password), username, session = TRUE)) expect_true(b_wincred_i_exists(target)) expect_equal(rawToChar(b_wincred_i_get(target)), password) expect_true(target %in% b_wincred_i_enumerate("*")) expect_silent(b_wincred_i_delete(target)) expect_false(b_wincred_i_exists(target)) expect_false(target %in% b_wincred_i_enumerate("*")) }) test_that("creating keychains", { skip_if_not_win() skip_on_cran() keyring <- random_keyring() kb <- backend_wincred$new(keyring = keyring) kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret123!") expect_true(keyring %in% kb$keyring_list()$keyring) list <- kb$list() expect_equal(nrow(list), 0) service <- random_service() username <- random_username() password <- random_password() expect_silent( kb$set_with_value(service, username, password) ) expect_equal(kb$get(service, username), password) expect_silent(kb$delete(service, username)) expect_silent(kb$keyring_delete(keyring = keyring)) expect_false(keyring %in% kb$keyring_list()$keyring) }) test_that("lock/unlock keyrings", { skip_if_not_win() skip_on_cran() keyring <- random_keyring() kb <- backend_wincred$new(keyring = keyring) kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret123!") ## It is unlocked by default expect_false(kb$keyring_is_locked()) list <- kb$keyring_list() expect_true(keyring %in% list$keyring) expect_false(list$locked[match(keyring, list$keyring)]) ## Lock it kb$keyring_lock() expect_true(kb$keyring_is_locked()) list <- keyring_list() expect_true(list$locked[match(keyring, list$keyring)]) ## Unlock it kb$keyring_unlock(password = "secret123!") expect_false(kb$keyring_is_locked()) list <- kb$keyring_list() expect_false(list$locked[match(keyring, list$keyring)]) expect_silent(kb$keyring_delete(keyring = keyring)) }) test_that(": in keyring, service and usernames", { skip_if_not_win() skip_on_cran() keyring <- paste0("foo:", random_keyring()) service <- paste0("bar:", random_service()) username <- paste0("foobar:", random_username()) password <- random_password() target <- b_wincred_target(keyring, service, username) expect_false(b_wincred_i_exists(target)) expect_silent(b_wincred_i_set(target, charToRaw(password), username, session = TRUE)) expect_true(b_wincred_i_exists(target)) expect_equal(rawToChar(b_wincred_i_get(target)), password) expect_true(target %in% b_wincred_i_enumerate("*")) expect_silent(b_wincred_i_delete(target)) expect_false(b_wincred_i_exists(target)) expect_false(target %in% b_wincred_i_enumerate("*")) }) keyring/tests/testthat/test-env.R0000644000176200001440000000200514146771774016633 0ustar liggesusers context("env keyring") test_that("set, get, delete", { service <- random_service() username <- random_username() password <- random_password() kb <- backend_env$new() var <- kb$.__enclos_env__$private$env_to_var(service, username) expect_silent( kb$set_with_value(service, username, password) ) expect_equal(kb$list(service)$username, c(username)) expect_equal(kb$get(service, username), password) expect_equal(Sys.getenv(var, "foo"), password) expect_silent(kb$delete(service, username)) expect_equal(Sys.getenv(var, "foo"), "foo") }) test_that("set, get, delete, without username", { service <- random_service() password <- random_password() kb <- backend_env$new() var <- kb$.__enclos_env__$private$env_to_var(service, NULL) expect_silent( kb$set_with_value(service, password = password) ) expect_equal(kb$get(service), password) expect_equal(Sys.getenv(var, "foo"), password) expect_silent(kb$delete(service)) expect_equal(Sys.getenv(var, "foo"), "foo") }) keyring/tests/testthat/test-default-backend.R0000644000176200001440000000416514146771774021065 0ustar liggesusers context("default backend") opts <- options(keyring_warn_for_env_fallback = FALSE) on.exit(options(opts), add = TRUE) test_that("use options", { withr::with_options( list(keyring_backend = "env"), expect_equal(default_backend(), backend_env$new()) ) withr::with_options( list(keyring_backend = "env"), expect_equal(default_backend(), backend_env$new()) ) ## This should run on all OSes currently, as we are not actually ## calling the Keychain API here. withr::with_options( list(keyring_backend = "macos", keyring_keyring = "foobar"), expect_equal(default_backend(), backend_macos$new(keyring = "foobar")) ) }) test_that("use env var", { ## Remove the options withr::with_options( list(keyring_backend = NULL, keyring_keyring = NULL), withr::with_envvar( c(R_KEYRING_BACKEND = "env"), expect_equal(default_backend(), backend_env$new()) ) ) }) test_that("mixing options and env vars", { ## Backend option, keyring env var withr::with_options( list(keyring_backend = "macos", keyring_keyring = NULL), withr::with_envvar( c(R_KEYRING_KEYRING = "foobar"), expect_equal(default_backend(), backend_macos$new(keyring = "foobar")) ) ) ## Backend env var, keyring option withr::with_options( list(keyring_backend = NULL, keyring_keyring = "foobar"), withr::with_envvar( c(R_KEYRING_BACKEND = "macos"), expect_equal(default_backend(), backend_macos$new(keyring = "foobar")) ) ) }) test_that("auto windows", { mockery::stub(default_backend_auto, "Sys.info", c(sysname = "Windows")) expect_equal(default_backend_auto(), backend_wincred) }) test_that("auto macos", { mockery::stub(default_backend_auto, "Sys.info", c(sysname = "Darwin")) expect_equal(default_backend_auto(), backend_macos) }) test_that("auto linux", { skip_if_not_linux() kb <- default_backend() expect_true(kb$name == "env" || kb$name == "secret service" || kb$name == "file") }) test_that("auto other", { mockery::stub(default_backend_auto, "Sys.info", c(sysname = "Solaris")) expect_equal(suppressWarnings(default_backend_auto()), backend_file) }) keyring/tests/testthat/test-secret-service.R0000644000176200001440000000376014146771774020777 0ustar liggesusers context("Secret Service API") opts <- options(keyring_warn_for_env_fallback = FALSE) on.exit(options(opts), add = TRUE) test_that("specify keyring explicitly", { skip_if_not_secret_service() skip_on_cran() service <- random_service() username <- random_username() password <- random_password() kb <- backend_secret_service$new("Login") expect_silent( kb$set_with_value(service, username, password) ) expect_equal(kb$get(service, username), password) expect_silent(kb$delete(service, username)) }) test_that("creating keychains", { skip("requires interaction") skip_if_not_secret_service() skip_on_cran() keyring <- random_keyring() kb <- backend_secret_service$new(keyring = keyring) kb$keyring_create(keyring = keyring) kb$keyring_list() service <- random_service() username <- random_username() password <- random_password() expect_silent( kb$set_with_value(service, username, password) ) expect_equal(kb$get(service, username), password) expect_silent(kb$delete(service, username)) expect_silent(kb$keyring_delete(keyring = keyring)) expect_false(keyring %in% keyring_list()$keyring) }) test_that("lock/unlock keyrings", { skip("requires interaction") skip_if_not_secret_service() skip_on_cran() keyring <- random_keyring() kb <- backend_secret_service$new(keyring = keyring) # interactive kb$.__enclos_env__$private$keyring_create_direct(keyring) ## It is unlocked by default expect_false(kb$keyring_is_locked()) list <- kb$keyring_list() expect_true(keyring %in% list$keyring) expect_false(list$locked[match(keyring, list$keyring)]) ## Lock it kb$keyring_lock() expect_true(kb$keyring_is_locked()) list <- kb$keyring_list() expect_true(list$locked[match(keyring, list$keyring)]) ## Unlock it (interactive) kb$keyring_unlock() expect_false(kb$keyring_is_locked()) list <- keyring_list() expect_false(list$locked[match(keyring, list$keyring)]) expect_silent(kb$keyring_delete(keyring = keyring)) }) keyring/tests/testthat/test-encoding.R0000644000176200001440000001000414146771774017627 0ustar liggesuserscontext("Testing encoding retrieval function") test_that("No option/env var set returns auto", { skip_if_not_win() withr::local_options(keyring.encoding_windows = NULL) withr::local_envvar(KEYRING_ENCODING_WINDOWS = NA_character_) encoding <- get_encoding_opt() expect_equal(encoding, "auto") }) test_that("Option encoding set and env unset returns option encoding", { skip_if_not_win() withr::local_options(keyring.encoding_windows = "UTF-16LE") withr::local_envvar(KEYRING_ENCODING_WINDOWS = NA_character_) encoding <- get_encoding_opt() expect_equal(encoding, "UTF-16LE") }) test_that("Option encoding unset and env set returns env encoding", { skip_if_not_win() withr::local_options(keyring.encoding_windows = NULL) withr::local_envvar("KEYRING_ENCODING_WINDOWS" = "UTF-8") encoding <- get_encoding_opt() expect_equal(encoding, "UTF-8") }) test_that("Option encoding set and env var set and EQUAL returns expected value", { skip_if_not_win() withr::local_options(keyring.encoding_windows = "UTF-16LE") withr::local_envvar("KEYRING_ENCODING_WINDOWS" = "UTF-16LE") encoding <- get_encoding_opt() expect_equal(encoding, "UTF-16LE") }) test_that("Invalid encoding (not in iconvlist) returns error", { skip_if_not_win() withr::local_options(keyring.encoding_windows = "doesnotexist") withr::local_envvar("KEYRING_ENCODING_WINDOWS" = "doesnotexist") expect_error(get_encoding_opt()) }) test_that("iconv suggestion works as expected", { skip_if_not_win() withr::local_options(keyring.encoding_windows = "UTF-16LP") withr::local_envvar("KEYRING_ENCODING_WINDOWS" = NA_character_) expect_error( get_encoding_opt(), "Encoding not found in iconvlist(). Did you mean UTF-16LE?", fixed = TRUE ) }) test_that("Option has precedence", { skip_if_not_win() withr::local_options(keyring.encoding_windows = iconvlist()[1]) withr::local_envvar("KEYRING_ENCODING_WINDOWS" = iconvlist()[2]) expect_identical(get_encoding_opt(), iconvlist()[1]) }) test_that("Set key with UTF-16LE encoding", { skip_if_not_win() skip_on_cran() service <- random_service() user <- random_username() pass <- random_password() # Now, set a key with UTF-16LE encoding using new options withr::local_options(keyring.encoding_windows = NULL) withr::local_envvar("KEYRING_ENCODING_WINDOWS" = "UTF-16LE") keyring::key_set_with_value(service = service, username = user, password = pass) # Get the password expect_equal(keyring::key_get(service = service, username = user), pass) # Show that it is UTF-16LE raw_password <- keyring:::b_wincred_i_get(target = paste0(":", service, ":", user)) expect_equal(iconv(list(raw_password), from = "UTF-16LE", to = ""), pass) key_delete(service = service, username = user) }) test_that("Set key with UTF-16LE encoding plus a keyring", { skip_if_not_win() skip_on_cran() withr::local_options(keyring.encoding_windows = NULL) withr::local_envvar("KEYRING_ENCODING_WINDOWS" = "UTF-16LE") keyring <- random_keyring() kb <- backend_wincred$new(keyring = keyring) kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret123!") expect_true(keyring %in% kb$keyring_list()$keyring) list <- kb$list() expect_equal(nrow(list), 0) service <- random_service() username <- random_username() password <- random_password() expect_silent( kb$set_with_value(service, username, password) ) expect_equal(kb$get(service, username), password) expect_silent(kb$delete(service, username)) expect_silent(kb$keyring_delete(keyring = keyring)) expect_false(keyring %in% kb$keyring_list()$keyring) }) test_that("marked UTF-8 strings work", { skip_if_not_win() skip_on_cran() withr::local_options(keyring.encoding_windows = "UTF-8") service <- random_service() user <- random_username() pass <- "this is ok: \u00bc" keyring::key_set_with_value(service = service, username = user, password = pass) # Get the password expect_equal(keyring::key_get(service = service, username = user), pass) key_delete(service = service, username = user) }) keyring/tests/testthat/test-file.R0000644000176200001440000002035514146771774016772 0ustar liggesusers context("file-based keyring") test_that("specify keyring explicitly", { dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE)) withr::local_options(list(keyring_file_dir = tmp)) service_1 <- random_service() username <- random_username() password <- random_password() password2 <- random_password() keyring <- random_keyring() kb <- backend_file$new(keyring = keyring) kb$keyring_create(keyring, "secret123!") expect_false(kb$keyring_is_locked(keyring)) kb$keyring_lock(keyring) expect_true(kb$keyring_is_locked(keyring)) expect_silent(kb$keyring_unlock(keyring, password = "secret123!")) expect_false(kb$keyring_is_locked(keyring)) ## Missing expect_error(kb$get(service_1, username), "could not be found") expect_silent(kb$set_with_value(service_1, username, password)) expect_equal(kb$get(service_1, username), password) ## Missing expect_error(kb$get(service_1, "foobar"), "could not be found") ## Overwrite expect_silent(kb$set_with_value(service_1, username, password2)) expect_equal(kb$get(service_1, username), password2) expect_silent(kb$set_with_value(random_service(), username, password)) long_password <- random_string(500L) service_2 <- random_service() expect_silent(kb$set_with_value(service_2, username, long_password)) expect_equal(kb$get(service_2, username), long_password) ## Delete expect_silent(kb$delete(service_1, username)) expect_error(kb$get(service_1, username), "could not be found") ## Delete non-existent is silent expect_silent(kb$delete(service_1, username)) expect_silent(kb$keyring_delete()) }) test_that("key consistency check", { username <- random_username() password <- random_password() keyring <- random_keyring() dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE), add = TRUE) withr::local_options(list(keyring_file_dir = tmp)) keyring_pwd_1 <- random_password() keyring_pwd_2 <- random_password() kb <- backend_file$new(keyring = keyring) kb$keyring_create(keyring, keyring_pwd_1) expect_silent(kb$keyring_unlock(password = keyring_pwd_1)) expect_silent(kb$set_with_value(random_service(), username, password)) expect_error(kb$keyring_unlock(password = keyring_pwd_2), "cannot unlock keyring") expect_silent(kb$keyring_lock()) expect_error(kb$keyring_unlock(password = keyring_pwd_2), "cannot unlock keyring") kb$.__enclos_env__$private$set_keyring_pass(keyring_pwd_2) expect_true(kb$keyring_is_locked()) kb$.__enclos_env__$private$unset_keyring_pass() with_mock(`keyring:::get_pass` = mockery::mock(keyring_pwd_1), { expect_silent(kb$set_with_value(random_service(), username, password)) }) expect_silent(kb$keyring_delete()) }) test_that("use non-default keyring", { dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE), add = TRUE) withr::local_options(list(keyring_file_dir = tmp)) service <- random_service() username <- random_username() password <- random_password() default_keyring <- random_keyring() keyring <- random_keyring() default_keyring_pwd <- random_password() keyring_pwd <- random_password() kb <- backend_file$new(keyring = default_keyring) kb$keyring_create(password = default_keyring_pwd) kb$keyring_create(keyring, keyring_pwd) expect_silent(kb$keyring_unlock(password = default_keyring_pwd)) expect_false(kb$keyring_is_locked()) expect_silent(kb$keyring_unlock(keyring, keyring_pwd)) expect_false(kb$keyring_is_locked(keyring)) expect_false(kb$keyring_is_locked()) expect_silent(kb$set_with_value(service, username, password, keyring)) expect_equal(kb$get(service, username, keyring), password) expect_silent( all_items <- kb$list(keyring = keyring) ) expect_is(all_items, "data.frame") expect_equal(nrow(all_items), 1L) expect_named(all_items, c("service", "username")) expect_silent(kb$keyring_delete()) expect_silent(kb$keyring_delete(keyring)) }) test_that("list keyring items", { dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE), add = TRUE) withr::local_options(list(keyring_file_dir = tmp)) service <- random_service() username <- random_username() keyring <- random_keyring() keyring_pwd <- random_password() kb <- backend_file$new(keyring) kb$keyring_create(keyring, keyring_pwd) expect_silent(kb$keyring_unlock(password = keyring_pwd)) expect_silent(kb$set_with_value(random_service(), random_username(), random_password())) expect_silent(kb$set_with_value(service, random_username(), random_password())) expect_silent(kb$set_with_value(service, random_username(), random_password())) expect_silent( all_items <- kb$list() ) expect_is(all_items, "data.frame") expect_equal(nrow(all_items), 3L) expect_named(all_items, c("service", "username")) expect_silent( some_items <- kb$list(service) ) expect_is(some_items, "data.frame") expect_equal(nrow(some_items), 2L) expect_named(some_items, c("service", "username")) invisible(sapply(some_items[["service"]], expect_identical, service)) expect_silent(kb$keyring_delete(keyring)) }) test_that("helper functions work", { secret <- random_password() long_secret <- random_string(500L) nonce <- sodium::random(24L) password <- sodium::hash(charToRaw(random_password())) expect_identical(b_file_split_string(secret), secret) expect_true( assertthat::is.string( split_key <- b_file_split_string(long_secret) ) ) expect_match(split_key, "\\n") expect_identical(b_file_merge_string(split_key), long_secret) expect_identical( b_file_secret_decrypt( b_file_secret_encrypt(secret, nonce, password), nonce, password ), secret ) expect_identical( b_file_secret_decrypt( b_file_secret_encrypt(long_secret, nonce, password), nonce, password ), long_secret ) }) test_that("keys updated from another session", { skip_on_cran() dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE), add = TRUE) withr::local_options(list(keyring_file_dir = tmp)) service_1 <- random_service() username <- random_username() username2 <- random_username() password <- random_password() password2 <- random_password() keyring <- random_keyring() kb <- backend_file$new(keyring = keyring) kb$keyring_create(keyring, "foobar") kb$keyring_unlock(password = "foobar") kb$set_with_value(service_1, username, password) ret <- callr::r(function(s, u, p, k, dir) { options(keyring_file_dir = dir) kb <- keyring::backend_file$new(keyring = k) kb$keyring_unlock(password = "foobar") kb$set_with_value(s, u, p) kb$get(s, u) }, args = list(s = service_1, u = username2, p = password2, k = keyring, dir = tmp)) expect_equal(ret, password2) expect_equal(kb$get(service_1, username), password) expect_equal(kb$get(service_1, username2), password2) expect_equal(kb$get(service_1, username), password) }) test_that("locking the keyring file", { skip_on_cran() dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE), add = TRUE) withr::local_options(list(keyring_file_dir = tmp)) service_1 <- random_service() username <- random_username() password <- random_password() keyring <- random_keyring() kb <- backend_file$new(keyring = keyring) kb$keyring_create(password = "foobar") lockfile <- paste0(kb$.__enclos_env__$private$keyring_file(), ".lck") rb <- callr::r_bg(function(lf) { l <- filelock::lock(lf) cat("done\n"); Sys.sleep(3) }, args = list(lf = lockfile), stdout = "|" ) on.exit(rb$kill(), add = TRUE) rb$poll_io(3000) withr::with_options( list(keyring_file_lock_timeout = 100), expect_error( kb$set_with_value(service_1, username, password), "Cannot lock keyring file") ) }) test_that("keyring does not exist", { dir.create(tmp <- tempfile()) on.exit(unlink(tmp, recursive = TRUE)) withr::local_options(list(keyring_file_dir = tmp)) kb <- backend_file$new() expect_error(kb$list()) expect_error(kb$keyring_is_locked()) expect_error(kb$keyring_unlock()) expect_error(kb$set_with_value("service", "user", "pass")) }) keyring/tests/testthat/helper.R0000644000176200001440000000246414146771774016356 0ustar liggesusers skip_if_not_macos <- function() { sysname <- tolower(Sys.info()[["sysname"]]) if (sysname != "darwin") skip("Not macOS") invisible(TRUE) } skip_if_not_win <- function() { sysname <- tolower(Sys.info()[["sysname"]]) if (sysname != "windows") skip("Not windows") invisible(TRUE) } skip_if_not_linux <- function() { sysname <- tolower(Sys.info()[["sysname"]]) if (sysname != "linux") skip("Not Linux") invisible(TRUE) } skip_if_not_secret_service <- function() { if (default_backend()$name != "secret service") skip("Not secret service") invisible(TRUE) } random_string <- function(length = 10, use_letters = TRUE, use_numbers = TRUE) { pool <- c( if (use_letters) c(letters, LETTERS), if (use_numbers) 0:9 ) paste( sample(pool, length, replace = TRUE), collapse = "" ) } random_service <- function() { paste0( "R-keyring-test-service-", random_string(15, use_numbers = FALSE) ) } random_username <- function() { random_string(10, use_numbers = FALSE) } random_password <- function() { random_string(16) } random_keyring <- function() { paste0( "Rkeyringtest", random_string(8, use_numbers = FALSE) ) } new_empty_dir <- function() { new <- tempfile() unlink(new, recursive = TRUE, force = TRUE) dir.create(new) new } keyring/tests/testthat/test-macos.R0000644000176200001440000000756714146771774017167 0ustar liggesusers context("macOS keyring") test_that("specify keyring explicitly", { skip_if_not_macos() skip_on_cran() service <- random_service() username <- random_username() password <- random_password() kb <- backend_macos$new(keyring = "login") expect_silent( kb$set_with_value(service, username, password) ) expect_equal(kb$get(service, username), password) expect_silent(kb$delete(service, username)) }) test_that("creating keychains", { skip_if_not_macos() skip_on_cran() keyring <- random_keyring() kb <- backend_macos$new() ## To avoid an interactive password kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret123!") list <- kb$list(keyring = keyring) expect_equal(nrow(list), 0) service <- random_service() username <- random_username() password <- random_password() expect_silent( kb$set_with_value(service, username, password) ) expect_equal(kb$get(service, username), password) expect_silent(kb$delete(service, username)) expect_silent(kb$keyring_delete(keyring = keyring)) expect_false(keyring %in% kb$keyring_list()$keyring) }) test_that("creating keychains 2", { skip_if_not_macos() skip_on_cran() keyring <- random_keyring() kb <- backend_macos$new() ## To avoid an interactive password kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret") kb$keyring_set_default(keyring) service <- random_service() username <- random_username() password <- random_password() expect_silent( kb$set_with_value(service, username, password) ) expect_equal(kb$get(service, username), password) expect_silent(kb$delete(service, username)) expect_silent(kb$keyring_delete(keyring = keyring)) expect_false(keyring %in% kb$keyring_list()$keyring) }) test_that("keyring file at special location", { skip_if_not_macos() skip_on_cran() keyring <- tempfile(fileext = ".keychain") kb <- backend_macos$new(keyring = keyring) kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret123!") service <- random_service() username <- random_username() password <- random_password() expect_silent( kb$set_with_value(service, username, password) ) expect_equal(kb$get(service, username), password) expect_silent(kb$delete(service, username)) expect_silent(kb$keyring_delete(keyring = keyring)) expect_false(keyring %in% kb$keyring_list()$keyring) expect_false(file.exists(keyring)) }) test_that("errors", { skip_if_not_macos() skip_on_cran() ## Non-existing keychain expect_error( backend_macos$new(tempfile())$list(), "cannot open keychain" ) ## Getting non-existing password expect_error( backend_macos$new()$get(random_service(), random_username()), "cannot get password" ) ## Deleting non-existing password expect_error( backend_macos$new()$delete(random_service(), random_username()), "cannot delete password" ) ## Create keychain without access to file kb <- backend_macos$new() expect_error( kb$.__enclos_env__$private$keyring_create_direct("/xxx", "secret123!"), "cannot create keychain" ) }) test_that("lock/unlock keyrings", { skip_if_not_macos() skip_on_cran() keyring <- random_keyring() kb <- backend_macos$new(keyring = keyring) kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret123!") ## It is unlocked by default expect_false(kb$keyring_is_locked()) list <- kb$keyring_list() expect_true(keyring %in% list$keyring) expect_false(list$locked[match(keyring, list$keyring)]) ## Lock it kb$keyring_lock() expect_true(kb$keyring_is_locked()) list <- kb$keyring_list() expect_true(list$locked[match(keyring, list$keyring)]) ## Unlock it kb$keyring_unlock(password = "secret123!") expect_false(kb$keyring_is_locked()) list <- kb$keyring_list() expect_false(list$locked[match(keyring, list$keyring)]) expect_silent(kb$keyring_delete(keyring = keyring)) }) keyring/tests/testthat/test-common.R0000644000176200001440000000375514146771774017350 0ustar liggesusers context("Common API") withr::local_options( keyring_warn_for_env_fallback = FALSE, keyring_file_dir = file.path(tempdir(), "keyrings") ) on.exit(unlink(file.path(tempdir(), "keyrings"), recursive = TRUE), add = TRUE) # The file backend needs a default keyring currently kb <- default_backend() if (kb$name == "file") { if (! "system" %in% kb$keyring_list()$keyring) { kb$.__enclos_env__$private$keyring_create_direct("system", "master") } kb$keyring_unlock("system", "master") } test_that("set, get, delete", { skip_on_cran() service <- random_service() username <- random_username() password <- random_password() expect_silent(key_set_with_value(service, username, password)) expect_equal(key_get(service, username), password) expect_silent(key_delete(service, username)) }) test_that("set, get, delete without username", { skip_on_cran() service <- random_service() password <- random_password() expect_silent(key_set_with_value(service, password = password)) expect_equal(key_get(service), password) expect_silent(key_delete(service)) }) test_that("set can update", { skip_on_cran() service <- random_service() username <- random_username() password <- random_password() expect_silent({ key_set_with_value(service, username, "foobar") key_set_with_value(service, username, password) }) expect_equal(key_get(service, username), password) expect_silent(key_delete(service, username)) }) test_that("list", { skip_on_cran() if (default_backend()$name == "env") skip("'env' backend has no 'list' support") service <- random_service() username <- random_username() password <- random_password() expect_silent({ key_set_with_value(service, username, password) list <- key_list() }) expect_equal(list$username[match(service, list$service)], username) list2 <- key_list(service = service) expect_equal(nrow(list2), 1) expect_equal(list2$username, username) expect_silent(key_delete(service, username)) }) keyring/tests/testthat.R0000644000176200001440000000007214146771774015070 0ustar liggesuserslibrary(testthat) library(keyring) test_check("keyring") keyring/src/0000755000176200001440000000000014150770251012513 5ustar liggesuserskeyring/src/init.c0000644000176200001440000000746414146771774013655 0ustar liggesusers #include #include #include SEXP keyring_macos_get(SEXP keyring, SEXP service, SEXP username); SEXP keyring_macos_set(SEXP keyring, SEXP service, SEXP username, SEXP password); SEXP keyring_macos_delete(SEXP keyring, SEXP service, SEXP username); SEXP keyring_macos_list(SEXP keyring, SEXP service); SEXP keyring_macos_create(SEXP keyring, SEXP password); SEXP keyring_macos_list_keyring(); SEXP keyring_macos_delete_keyring(SEXP keyring); SEXP keyring_macos_lock_keyring(SEXP keyring); SEXP keyring_macos_unlock_keyring(SEXP keyring, SEXP password); SEXP keyring_macos_is_locked_keyring(SEXP keyring); SEXP keyring_wincred_get(SEXP); SEXP keyring_wincred_exists(SEXP); SEXP keyring_wincred_set(SEXP, SEXP, SEXP, SEXP); SEXP keyring_wincred_delete(SEXP); SEXP keyring_wincred_enumerate(SEXP); SEXP keyring_secret_service_is_available(SEXP); SEXP keyring_secret_service_get(SEXP, SEXP, SEXP); SEXP keyring_secret_service_set(SEXP, SEXP, SEXP, SEXP); SEXP keyring_secret_service_delete(SEXP, SEXP, SEXP); SEXP keyring_secret_service_list(SEXP, SEXP); SEXP keyring_secret_service_create_keyring(SEXP); SEXP keyring_secret_service_list_keyring(); SEXP keyring_secret_service_delete_keyring(SEXP); SEXP keyring_secret_service_lock_keyring(SEXP); SEXP keyring_secret_service_unlock_keyring(SEXP, SEXP); SEXP keyring_secret_service_is_locked_keyring(SEXP); static const R_CallMethodDef callMethods[] = { { "keyring_macos_get", (DL_FUNC) &keyring_macos_get, 3 }, { "keyring_macos_set", (DL_FUNC) &keyring_macos_set, 4 }, { "keyring_macos_delete", (DL_FUNC) &keyring_macos_delete, 3 }, { "keyring_macos_list", (DL_FUNC) &keyring_macos_list, 2 }, { "keyring_macos_create", (DL_FUNC) &keyring_macos_create, 2 }, { "keyring_macos_list_keyring", (DL_FUNC) &keyring_macos_list_keyring, 0 }, { "keyring_macos_delete_keyring", (DL_FUNC) &keyring_macos_delete_keyring, 1 }, { "keyring_macos_lock_keyring", (DL_FUNC) &keyring_macos_lock_keyring, 1 }, { "keyring_macos_unlock_keyring", (DL_FUNC) &keyring_macos_unlock_keyring, 2 }, { "keyring_macos_is_locked_keyring", (DL_FUNC) &keyring_macos_is_locked_keyring, 1 }, { "keyring_wincred_get", (DL_FUNC) &keyring_wincred_get, 1 }, { "keyring_wincred_exists", (DL_FUNC) &keyring_wincred_exists, 1 }, { "keyring_wincred_set", (DL_FUNC) &keyring_wincred_set, 4 }, { "keyring_wincred_delete", (DL_FUNC) &keyring_wincred_delete, 1 }, { "keyring_wincred_enumerate", (DL_FUNC) &keyring_wincred_enumerate, 1 }, { "keyring_secret_service_is_available", (DL_FUNC) &keyring_secret_service_is_available, 1 }, { "keyring_secret_service_get", (DL_FUNC) &keyring_secret_service_get, 3 }, { "keyring_secret_service_set", (DL_FUNC) &keyring_secret_service_set, 4 }, { "keyring_secret_service_delete", (DL_FUNC) &keyring_secret_service_delete, 3 }, { "keyring_secret_service_list", (DL_FUNC) &keyring_secret_service_list, 2 }, { "keyring_secret_service_create_keyring", (DL_FUNC) &keyring_secret_service_create_keyring, 1 }, { "keyring_secret_service_list_keyring", (DL_FUNC) &keyring_secret_service_list_keyring, 0 }, { "keyring_secret_service_delete_keyring", (DL_FUNC) &keyring_secret_service_delete_keyring, 1 }, { "keyring_secret_service_lock_keyring", (DL_FUNC) &keyring_secret_service_lock_keyring, 1 }, { "keyring_secret_service_unlock_keyring", (DL_FUNC) &keyring_secret_service_unlock_keyring, 2 }, { "keyring_secret_service_is_locked_keyring", (DL_FUNC) &keyring_secret_service_is_locked_keyring, 1 }, { NULL, NULL, 0 } }; void R_init_keyring(DllInfo *dll) { R_registerRoutines(dll, NULL, callMethods, NULL, NULL); R_useDynamicSymbols(dll, FALSE); R_forceSymbols(dll, TRUE); } keyring/src/keyring_secret_service.c0000644000176200001440000004040014146771774017432 0ustar liggesusers /* Avoid warning about empty compilation unit. */ void keyring_secret_service_dummy() { } #if defined(__linux__) && defined(HAS_LIBSECRET) #include #include #include #define SECRET_WITH_UNSTABLE 1 #define SECRET_API_SUBJECT_TO_CHANGE 1 #include const SecretSchema *keyring_secret_service_schema() { static const SecretSchema schema = { "com.rstudio.keyring.password", SECRET_SCHEMA_NONE, { { "service", SECRET_SCHEMA_ATTRIBUTE_STRING }, { "username", SECRET_SCHEMA_ATTRIBUTE_STRING }, { NULL, 0 }, } }; return &schema; } void keyring_secret_service_handle_status(const char *func, gboolean status, GError *err) { if (!status || err) { char *msg = R_alloc(1, strlen(err->message) + 1); strcpy(msg, err->message); g_error_free (err); error("Secret service keyring error in '%s': '%s'", func, msg); } } SEXP keyring_secret_service_is_available(SEXP report_error) { GError *err = NULL; SecretService *secretservice = secret_service_get_sync( /* flags = */ SECRET_SERVICE_LOAD_COLLECTIONS | SECRET_SERVICE_OPEN_SESSION, /* cancellable = */ NULL, &err); if (err || !secretservice) { if (LOGICAL(report_error)[0]) { keyring_secret_service_handle_status("is_available", TRUE, err); error("Cannot connect to secret service"); } else { return ScalarLogical(0); } } return ScalarLogical(1); } SecretCollection* keyring_secret_service_get_collection_default() { SecretCollection *collection = NULL; GError *err = NULL; SecretService *secretservice = secret_service_get_sync( /* flags = */ SECRET_SERVICE_LOAD_COLLECTIONS | SECRET_SERVICE_OPEN_SESSION, /* cancellable = */ NULL, &err); if (err || !secretservice) { keyring_secret_service_handle_status("get_keyring", TRUE, err); error("Cannot connect to secret service"); } collection = secret_collection_for_alias_sync( /* service = */ secretservice, /* alias = */ "default", /* flags = */ SECRET_COLLECTION_NONE, /* cancellable = */ NULL, &err); g_object_unref(secretservice); if (err || !collection) { keyring_secret_service_handle_status("get_keyring", TRUE, err); error("Cannot find keyring"); } return collection; } GList* keyring_secret_service_list_collections() { GError *err = NULL; SecretService *secretservice = secret_service_get_sync( /* flags = */ SECRET_SERVICE_LOAD_COLLECTIONS | SECRET_SERVICE_OPEN_SESSION, /* cancellable = */ NULL, &err); if (err || !secretservice) { keyring_secret_service_handle_status("create_keyring", TRUE, err); error("Cannot connect to secret service"); } gboolean status = secret_service_load_collections_sync( secretservice, /* cancellable = */ NULL, &err); if (status || err) { keyring_secret_service_handle_status("create_keyring", status, err); } GList *collections = secret_service_get_collections(secretservice); if (!collections) { g_object_unref(secretservice); error("Cannot query keyrings"); } g_object_unref(secretservice); return collections; } SecretCollection* keyring_secret_service_get_collection_other(const char *name) { GList *collections = keyring_secret_service_list_collections(); GList *item; for (item = g_list_first(collections); item; item = g_list_next(item)) { SecretCollection *coll = item->data; gchar *label = secret_collection_get_label(coll); if (! g_strcmp0(label, name)) { SecretCollection *copy = g_object_ref(coll); g_list_free(collections); return copy; } } g_list_free(collections); error("Did not find collection: '%s'", name); return NULL; } SecretCollection* keyring_secret_service_get_collection(SEXP keyring) { if (isNull(keyring)) { return keyring_secret_service_get_collection_default(); } else { const char *ckeyring = CHAR(STRING_ELT(keyring, 0)); return keyring_secret_service_get_collection_other(ckeyring); } } GList* keyring_secret_service_get_item(SEXP keyring, SEXP service, SEXP username) { const char* empty = ""; const char* cservice = CHAR(STRING_ELT(service, 0)); const char* cusername = isNull(username) ? empty : CHAR(STRING_ELT(username, 0)); const char *errormsg = NULL; SecretCollection *collection = NULL; GList *secretlist = NULL; GHashTable *attributes = NULL; GError *err = NULL; collection = keyring_secret_service_get_collection(keyring); attributes = g_hash_table_new( /* hash_func = */ (GHashFunc) g_str_hash, /* key_equal_func = */ (GEqualFunc) g_str_equal); g_hash_table_insert(attributes, g_strdup("service"), g_strdup(cservice)); g_hash_table_insert(attributes, g_strdup("username"), g_strdup(cusername)); secretlist = secret_collection_search_sync( /* self = */ collection, /* schema = */ keyring_secret_service_schema(), /* attributes = */ attributes, /* flags = */ SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK | SECRET_SEARCH_LOAD_SECRETS, /* cancellable = */ NULL, &err); if (collection) g_object_unref(collection); if (attributes) g_hash_table_unref(attributes); keyring_secret_service_handle_status("get", TRUE, err); if (errormsg) error(errormsg); return secretlist; } SEXP keyring_secret_service_get(SEXP keyring, SEXP service, SEXP username) { GList *secretlist = keyring_secret_service_get_item(keyring, service, username); guint listlength = g_list_length(secretlist); if (listlength == 0) { g_list_free(secretlist); error("keyring item not found"); } else if (listlength > 1) { warning("Multiple matching keyring items found, returning first"); } SecretItem *secretitem = g_list_first(secretlist)->data; SecretValue *secretvalue = secret_item_get_secret(secretitem); if (!secretvalue) { g_list_free(secretlist); error("Cannot get password"); } gsize passlength; const gchar *password = secret_value_get(secretvalue, &passlength); SEXP result = PROTECT(allocVector(RAWSXP, passlength)); memcpy(RAW(result), password, passlength); g_list_free(secretlist); UNPROTECT(1); return result; } SEXP keyring_secret_service_set(SEXP keyring, SEXP service, SEXP username, SEXP password) { const char* empty = ""; const char* cservice = CHAR(STRING_ELT(service, 0)); const char* cusername = isNull(username) ? empty : CHAR(STRING_ELT(username, 0)); SecretCollection *collection = NULL; GHashTable *attributes = NULL; GError *err = NULL; collection = keyring_secret_service_get_collection(keyring); attributes = g_hash_table_new( /* hash_func = */ (GHashFunc) g_str_hash, /* key_equal_func = */ (GEqualFunc) g_str_equal); g_hash_table_insert(attributes, g_strdup("service"), g_strdup(cservice)); g_hash_table_insert(attributes, g_strdup("username"), g_strdup(cusername)); SecretValue *value = secret_value_new((gchar *)RAW(password), LENGTH(password), /* content_type = */ "text/plain"); SecretItem *item = secret_item_create_sync( collection, keyring_secret_service_schema(), attributes, /* label = */ cservice, value, /* flags = */ SECRET_ITEM_CREATE_REPLACE, /* cancellable = */ NULL, &err); if (item) g_object_unref(item); keyring_secret_service_handle_status("set", TRUE, err); return R_NilValue; } SEXP keyring_secret_service_delete(SEXP keyring, SEXP service, SEXP username) { GList *secretlist = keyring_secret_service_get_item(keyring, service, username); guint listlength = g_list_length(secretlist); if (listlength == 0) { g_list_free(secretlist); error("keyring item not found"); } else if (listlength > 1) { warning("Multiple matching keyring items found, returning first"); } SecretItem *secretitem = g_list_first(secretlist)->data; GError *err = NULL; gboolean status = secret_item_delete_sync( secretitem, /* cancellable = */ NULL, &err); g_list_free(secretlist); if (!status) error("Could not delete keyring item"); keyring_secret_service_handle_status("delete", status, err); return R_NilValue; } SEXP keyring_secret_service_list(SEXP keyring, SEXP service) { const char *cservice = isNull(service) ? NULL : CHAR(STRING_ELT(service, 0)); const char *errormsg = NULL; GList *secretlist = NULL, *iter = NULL; guint listlength, i; GHashTable *attributes = NULL; GError *err = NULL; SEXP result = R_NilValue; SecretCollection *collection = keyring_secret_service_get_collection(keyring); /* If service is not NULL, then we only look for the specified service. */ attributes = g_hash_table_new( /* hash_func = */ (GHashFunc) g_str_hash, /* key_equal_func = */ (GEqualFunc) g_str_equal); if (cservice) { g_hash_table_insert(attributes, g_strdup("service"), g_strdup(cservice)); } secretlist = secret_collection_search_sync( /* self = */ collection, /* schema = */ keyring_secret_service_schema(), /* attributes = */ attributes, /* flags = */ SECRET_SEARCH_ALL, /* cancellable = */ NULL, &err); if (err) goto cleanup; listlength = g_list_length(secretlist); result = PROTECT(allocVector(VECSXP, 2)); SET_VECTOR_ELT(result, 0, allocVector(STRSXP, listlength)); SET_VECTOR_ELT(result, 1, allocVector(STRSXP, listlength)); for (i = 0, iter = g_list_first(secretlist); iter; i++, iter = g_list_next(iter)) { SecretItem *secret = iter->data; GHashTable *attr = secret_item_get_attributes(secret); char *service = g_hash_table_lookup(attr, "service"); char *username = g_hash_table_lookup(attr, "username"); SET_STRING_ELT(VECTOR_ELT(result, 0), i, mkChar(service)); SET_STRING_ELT(VECTOR_ELT(result, 1), i, mkChar(username)); } UNPROTECT(1); /* If an error happened, then err is not NULL, and the handler longjumps. Otherwise if errormsg is not NULL, then we error out with that. This happens for example if the specified keyring is not found. */ cleanup: if (collection) g_object_unref(collection); if (secretlist) g_list_free(secretlist); if (attributes) g_hash_table_unref(attributes); keyring_secret_service_handle_status("list", TRUE, err); if (errormsg) error(errormsg); return result; } SEXP keyring_secret_service_create_keyring(SEXP keyring) { const char *ckeyring = CHAR(STRING_ELT(keyring, 0)); GError *err = NULL; SecretService *secretservice = secret_service_get_sync( /* flags = */ SECRET_SERVICE_LOAD_COLLECTIONS | SECRET_SERVICE_OPEN_SESSION, /* cancellable = */ NULL, &err); if (err || !secretservice) { keyring_secret_service_handle_status("create_keyring", TRUE, err); error("Cannot connect to secret service"); } SecretCollection *collection = secret_collection_create_sync( /* service = */ secretservice, /* label = */ ckeyring, /* alias = */ NULL, /* flags = */ 0, /* cancellable = */ NULL, &err); g_object_unref(secretservice); keyring_secret_service_handle_status("create_keyring", TRUE, err); if (collection) g_object_unref(collection); /* Need to disconnect here, otherwise the proxy is cached, and the new collection is not in this cache. If we disconnect here, a new proxy will be created for the next operation, and this will already include the new collection. */ secret_service_disconnect(); return R_NilValue; } SEXP keyring_secret_service_list_keyring() { GList *collections = keyring_secret_service_list_collections(); guint num = g_list_length(collections); SEXP result = PROTECT(allocVector(VECSXP, 3)); SET_VECTOR_ELT(result, 0, allocVector(STRSXP, num)); SET_VECTOR_ELT(result, 1, allocVector(INTSXP, num)); SET_VECTOR_ELT(result, 2, allocVector(LGLSXP, num)); GList *item; int i = 0; for (item = g_list_first(collections); item; item = g_list_next(item), i++) { SecretCollection *coll = item->data; gchar *label = secret_collection_get_label(coll); gboolean locked = secret_collection_get_locked(coll); GList *secrets = secret_collection_get_items(coll); SET_STRING_ELT(VECTOR_ELT(result, 0), i, mkChar((char*) label)); INTEGER(VECTOR_ELT(result, 1))[i] = g_list_length(secrets); LOGICAL(VECTOR_ELT(result, 2))[i] = locked; } g_list_free(collections); UNPROTECT(1); return result; } SEXP keyring_secret_service_delete_keyring(SEXP keyring) { if (isNull(keyring)) error("Cannot delete the default keyring"); const char *ckeyring = CHAR(STRING_ELT(keyring, 0)); SecretCollection* collection = keyring_secret_service_get_collection_other(ckeyring); GError *err = NULL; gboolean status = secret_collection_delete_sync( collection, /* cancellable = */ NULL, &err); g_object_unref(collection); keyring_secret_service_handle_status("delete_keyring", status, err); /* Need to disconnect here, otherwise the proxy is cached, and the deleted collection will be still in the cache. If we disconnect here, a new proxy will be created for the next operation, and this will not include the deleted collection. */ secret_service_disconnect(); return R_NilValue; } SEXP keyring_secret_service_lock_keyring(SEXP keyring) { SecretCollection *collection = keyring_secret_service_get_collection(keyring); GList *list = g_list_append(NULL, collection); GError *err = NULL; gint ret = secret_service_lock_sync( /* service = */ NULL, /* objects = */ list, /* cancellable = */ NULL, /* locked = */ NULL, &err); g_list_free(list); keyring_secret_service_handle_status("lock_keyring", TRUE, err); if (ret == 0) { error("Could not lock keyring"); } return R_NilValue; } SEXP keyring_secret_service_unlock_keyring(SEXP keyring, SEXP password) { SecretCollection *collection = keyring_secret_service_get_collection(keyring); GList *list = g_list_append(NULL, collection); GError *err = NULL; gint ret = secret_service_unlock_sync( /* service = */ NULL, /* objects = */ list, /* cancellable = */ NULL, /* unlocked = */ NULL, &err); g_list_free(list); keyring_secret_service_handle_status("unlock_keyring", TRUE, err); if (ret == 0) { error("Could not unlock keyring"); } return R_NilValue; } SEXP keyring_secret_service_is_locked_keyring(SEXP keyring) { SecretCollection *collection = keyring_secret_service_get_collection(keyring); gboolean locked = secret_collection_get_locked(collection); return ScalarLogical(locked); } void R_unload_keyring(DllInfo *dll) { secret_service_disconnect(); } #else #include #include #include SEXP keyring_secret_service_is_available(SEXP report_error) { #ifdef __linux__ if (LOGICAL(report_error)[0]) { error("keyring build has no libsecret support"); } else { return ScalarLogical(0); } #else error("only works on Linux"); return R_NilValue; #endif } SEXP keyring_secret_service_get(SEXP keyring, SEXP service, SEXP username) { error("only works on Linux with Secret Service support"); return R_NilValue; } SEXP keyring_secret_service_set(SEXP keyring, SEXP service, SEXP username, SEXP password) { error("only works on Linux with Secret Service support"); return R_NilValue; } SEXP keyring_secret_service_delete(SEXP keyring, SEXP service, SEXP username) { error("only works on Linux with Secret Service support"); return R_NilValue; } SEXP keyring_secret_service_list(SEXP keyring, SEXP service) { error("only works on Linux with Secret Service support"); return R_NilValue; } SEXP keyring_secret_service_create_keyring(SEXP keyring) { error("only works on Linux with Secret Service support"); return R_NilValue; } SEXP keyring_secret_service_list_keyring() { error("only works on Linux with Secret Service support"); return R_NilValue; } SEXP keyring_secret_service_delete_keyring(SEXP keyring) { error("only works on Linux with Secret Service support"); return R_NilValue; } SEXP keyring_secret_service_lock_keyring(SEXP keyring) { error("only works on Linux with Secret Service support"); return R_NilValue; } SEXP keyring_secret_service_unlock_keyring(SEXP keyring, SEXP password) { error("only works on Linux with Secret Service support"); return R_NilValue; } SEXP keyring_secret_service_is_locked_keyring(SEXP keyring) { error("only works on Linux with Secret Service support"); return R_NilValue; } #endif // __linux__ keyring/src/keyring_macos.c0000644000176200001440000004017514146771774015540 0ustar liggesusers #ifdef __APPLE__ #include #include #include #include #include #include #include void keyring_macos_error(const char *func, OSStatus status) { CFStringRef str = SecCopyErrorMessageString(status, NULL); CFIndex length = CFStringGetLength(str); CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; char *buffer = R_alloc(maxSize, 1); if (CFStringGetCString(str, buffer, maxSize, kCFStringEncodingUTF8)) { error("keyring error (macOS Keychain), %s: %s", func, buffer); } else { error("keyring error (macOS Keychain), %s", func); } } void keyring_macos_handle_status(const char *func, OSStatus status) { if (status != errSecSuccess) keyring_macos_error(func, status); } SecKeychainRef keyring_macos_open_keychain(const char *pathName) { SecKeychainRef keychain; # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" OSStatus status = SecKeychainOpen(pathName, &keychain); # pragma GCC diagnostic pop keyring_macos_handle_status("cannot open keychain", status); /* We need to query the status, because SecKeychainOpen succeeds, even if the keychain file does not exists. (!) */ SecKeychainStatus keychainStatus = 0; status = SecKeychainGetStatus(keychain, &keychainStatus); keyring_macos_handle_status("cannot open keychain", status); return keychain; } SEXP keyring_macos_get(SEXP keyring, SEXP service, SEXP username) { const char* empty = ""; const char* cservice = CHAR(STRING_ELT(service, 0)); const char* cusername = isNull(username) ? empty :CHAR(STRING_ELT(username, 0)); void *data; UInt32 length; SEXP result; SecKeychainRef keychain = isNull(keyring) ? NULL : keyring_macos_open_keychain(CHAR(STRING_ELT(keyring, 0))); OSStatus status = SecKeychainFindGenericPassword( keychain, (UInt32) strlen(cservice), cservice, (UInt32) strlen(cusername), cusername, &length, &data, /* itemRef = */ NULL); if (keychain != NULL) CFRelease(keychain); keyring_macos_handle_status("cannot get password", status); result = PROTECT(allocVector(RAWSXP, length)); memcpy(RAW(result), data, length); SecKeychainItemFreeContent(NULL, data); UNPROTECT(1); return result; } SEXP keyring_macos_set(SEXP keyring, SEXP service, SEXP username, SEXP password) { const char* empty = ""; const char* cservice = CHAR(STRING_ELT(service, 0)); const char* cusername = isNull(username) ? empty : CHAR(STRING_ELT(username, 0)); SecKeychainItemRef item; SecKeychainRef keychain = isNull(keyring) ? NULL : keyring_macos_open_keychain(CHAR(STRING_ELT(keyring, 0))); /* Try to find it, and it is exists, update it */ OSStatus status = SecKeychainFindGenericPassword( keychain, (UInt32) strlen(cservice), cservice, (UInt32) strlen(cusername), cusername, /* passwordLength = */ NULL, /* passwordData = */ NULL, &item); if (status == errSecItemNotFound) { status = SecKeychainAddGenericPassword( keychain, (UInt32) strlen(cservice), cservice, (UInt32) strlen(cusername), cusername, (UInt32) LENGTH(password), RAW(password), /* itemRef = */ NULL); } else { status = SecKeychainItemModifyAttributesAndData( item, /* attrList= */ NULL, (UInt32) LENGTH(password), RAW(password)); CFRelease(item); } if (keychain != NULL) CFRelease(keychain); keyring_macos_handle_status("cannot set password", status); return R_NilValue; } SEXP keyring_macos_delete(SEXP keyring, SEXP service, SEXP username) { const char* empty = ""; const char* cservice = CHAR(STRING_ELT(service, 0)); const char* cusername = isNull(username) ? empty : CHAR(STRING_ELT(username, 0)); SecKeychainRef keychain = isNull(keyring) ? NULL : keyring_macos_open_keychain(CHAR(STRING_ELT(keyring, 0))); SecKeychainItemRef item; OSStatus status = SecKeychainFindGenericPassword( keychain, (UInt32) strlen(cservice), cservice, (UInt32) strlen(cusername), cusername, /* *passwordLength = */ NULL, /* *passwordData = */ NULL, &item); if (status != errSecSuccess) { if (keychain != NULL) CFRelease(keychain); keyring_macos_error("cannot delete password", status); } status = SecKeychainItemDelete(item); if (status != errSecSuccess) { if (keychain != NULL) CFRelease(keychain); keyring_macos_error("cannot delete password", status); } if (keychain != NULL) CFRelease(keychain); CFRelease(item); return R_NilValue; } static void keyring_macos_list_item(SecKeychainItemRef item, SEXP result, int idx) { SecItemClass class; SecKeychainAttribute attrs[] = { { kSecServiceItemAttr }, { kSecAccountItemAttr } }; SecKeychainAttributeList attrList = { 2, attrs }; /* This should not happen, not a keychain... */ if (SecKeychainItemGetTypeID() != CFGetTypeID(item)) { SET_STRING_ELT(VECTOR_ELT(result, 0), idx, mkChar("")); SET_STRING_ELT(VECTOR_ELT(result, 1), idx, mkChar("")); return; } OSStatus status = SecKeychainItemCopyContent(item, &class, &attrList, /* length = */ NULL, /* outData = */ NULL); keyring_macos_handle_status("cannot list passwords", status); SET_STRING_ELT(VECTOR_ELT(result, 0), idx, mkCharLen(attrs[0].data, attrs[0].length)); SET_STRING_ELT(VECTOR_ELT(result, 1), idx, mkCharLen(attrs[1].data, attrs[1].length)); SecKeychainItemFreeContent(&attrList, NULL); } CFArrayRef keyring_macos_list_get(const char *ckeyring, const char *cservice) { CFStringRef cfservice = NULL; CFMutableDictionaryRef query = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll); CFDictionarySetValue(query, kSecReturnData, kCFBooleanFalse); CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue); CFDictionarySetValue(query, kSecClass, kSecClassGenericPassword); CFArrayRef searchList = NULL; if (ckeyring) { SecKeychainRef keychain = keyring_macos_open_keychain(ckeyring); searchList = CFArrayCreate(NULL, (const void **) &keychain, 1, &kCFTypeArrayCallBacks); CFDictionaryAddValue(query, kSecMatchSearchList, searchList); } if (cservice) { cfservice = CFStringCreateWithBytes( /* alloc = */ NULL, (const UInt8*) cservice, strlen(cservice), kCFStringEncodingUTF8, /* isExternalRepresentation = */ 0); CFDictionaryAddValue(query, kSecAttrService, cfservice); } CFArrayRef resArray = NULL; OSStatus status = SecItemCopyMatching(query, (CFTypeRef*) &resArray); CFRelease(query); if (cfservice != NULL) CFRelease(cfservice); if (searchList != NULL) CFRelease(searchList); /* If there are no elements in the keychain, then SecItemCopyMatching returns with an error, so we need work around that and return an empty list instead. */ if (status == errSecItemNotFound) { resArray = CFArrayCreate(NULL, NULL, 0, NULL); return resArray; } else if (status != errSecSuccess) { if (resArray != NULL) CFRelease(resArray); keyring_macos_handle_status("cannot list passwords", status); return NULL; } else { return resArray; } } SEXP keyring_macos_list(SEXP keyring, SEXP service) { const char *ckeyring = isNull(keyring) ? NULL : CHAR(STRING_ELT(keyring, 0)); const char *cservice = isNull(service) ? NULL : CHAR(STRING_ELT(service, 0)); CFArrayRef resArray = keyring_macos_list_get(ckeyring, cservice); CFIndex i, num = CFArrayGetCount(resArray); SEXP result; PROTECT(result = allocVector(VECSXP, 2)); SET_VECTOR_ELT(result, 0, allocVector(STRSXP, num)); SET_VECTOR_ELT(result, 1, allocVector(STRSXP, num)); for (i = 0; i < num; i++) { SecKeychainItemRef item = (SecKeychainItemRef) CFArrayGetValueAtIndex(resArray, i); keyring_macos_list_item(item, result, (int) i); } CFRelease(resArray); UNPROTECT(1); return result; } SEXP keyring_macos_create(SEXP keyring, SEXP password) { const char *ckeyring = CHAR(STRING_ELT(keyring, 0)); const char *cpassword = CHAR(STRING_ELT(password, 0)); SecKeychainRef result = NULL; # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" OSStatus status = SecKeychainCreate( # pragma GCC diagnostic pop ckeyring, /* passwordLength = */ (UInt32) strlen(cpassword), (const void*) cpassword, /* promptUser = */ 0, /* initialAccess = */ NULL, &result); keyring_macos_handle_status("cannot create keychain", status); CFArrayRef keyrings = NULL; status = SecKeychainCopyDomainSearchList( kSecPreferencesDomainUser, &keyrings); if (status) { # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" SecKeychainDelete(result); # pragma GCC diagnostic pop if (result != NULL) CFRelease(result); keyring_macos_handle_status("cannot create keychain", status); } /* We need to add the new keychain to the keychain search list, otherwise applications like Keychain Access will not see it. There is no API to append it, we need to query the current search list, add it, and then set the whole new search list. This is of course a race condition. :/ */ CFIndex count = CFArrayGetCount(keyrings); CFMutableArrayRef newkeyrings = CFArrayCreateMutableCopy(NULL, count + 1, keyrings); CFArrayAppendValue(newkeyrings, result); status = SecKeychainSetDomainSearchList( kSecPreferencesDomainUser, newkeyrings); if (status) { # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" SecKeychainDelete(result); # pragma GCC diagnostic pop if (result) CFRelease(result); if (keyrings) CFRelease(keyrings); if (newkeyrings) CFRelease(newkeyrings); keyring_macos_handle_status("cannot create keychain", status); } CFRelease(result); CFRelease(keyrings); CFRelease(newkeyrings); return R_NilValue; } SEXP keyring_macos_list_keyring() { CFArrayRef keyrings = NULL; OSStatus status = SecKeychainCopyDomainSearchList(kSecPreferencesDomainUser, &keyrings); keyring_macos_handle_status("cannot list keyrings", status); CFIndex i, num = CFArrayGetCount(keyrings); SEXP result = PROTECT(allocVector(VECSXP, 3)); SET_VECTOR_ELT(result, 0, allocVector(STRSXP, num)); SET_VECTOR_ELT(result, 1, allocVector(INTSXP, num)); SET_VECTOR_ELT(result, 2, allocVector(LGLSXP, num)); for (i = 0; i < num; i++) { SecKeychainRef keychain = (SecKeychainRef) CFArrayGetValueAtIndex(keyrings, i); UInt32 pathLength = MAXPATHLEN; char pathName[MAXPATHLEN + 1]; # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" status = SecKeychainGetPath(keychain, &pathLength, pathName); # pragma GCC diagnostic pop pathName[pathLength] = '\0'; if (status) { CFRelease(keyrings); keyring_macos_handle_status("cannot list keyrings", status); } SET_STRING_ELT(VECTOR_ELT(result, 0), i, mkCharLen(pathName, pathLength)); CFArrayRef resArray = keyring_macos_list_get(pathName, /* cservice = */ NULL); CFIndex numitems = CFArrayGetCount(resArray); CFRelease(resArray); INTEGER(VECTOR_ELT(result, 1))[i] = (int) numitems; SecKeychainStatus kstatus; status = SecKeychainGetStatus(keychain, &kstatus); if (status) { LOGICAL(VECTOR_ELT(result, 2))[i] = NA_LOGICAL; } else { LOGICAL(VECTOR_ELT(result, 2))[i] = ! (kstatus & kSecUnlockStateStatus); } } CFRelease(keyrings); UNPROTECT(1); return result; } SEXP keyring_macos_delete_keyring(SEXP keyring) { const char *ckeyring = CHAR(STRING_ELT(keyring, 0)); /* Need to remove it from the search list as well */ CFArrayRef keyrings = NULL; OSStatus status = SecKeychainCopyDomainSearchList( kSecPreferencesDomainUser, &keyrings); keyring_macos_handle_status("cannot delete keyring", status); CFIndex i, count = CFArrayGetCount(keyrings); CFMutableArrayRef newkeyrings = CFArrayCreateMutableCopy(NULL, count, keyrings); for (i = 0; i < count; i++) { SecKeychainRef item = (SecKeychainRef) CFArrayGetValueAtIndex(keyrings, i); UInt32 pathLength = MAXPATHLEN; char pathName[MAXPATHLEN + 1]; # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" status = SecKeychainGetPath(item, &pathLength, pathName); # pragma GCC diagnostic pop pathName[pathLength] = '\0'; if (status) { CFRelease(keyrings); CFRelease(newkeyrings); keyring_macos_handle_status("cannot delete keyring", status); } if (!strcmp(pathName, ckeyring)) { CFArrayRemoveValueAtIndex(newkeyrings, (CFIndex) i); status = SecKeychainSetDomainSearchList( kSecPreferencesDomainUser, newkeyrings); if (status) { CFRelease(keyrings); CFRelease(newkeyrings); keyring_macos_handle_status("cannot delete keyring", status); } } } /* If we haven't found it on the search list, then we just keep silent about it ... */ CFRelease(keyrings); CFRelease(newkeyrings); /* And now remove the file as well... */ SecKeychainRef keychain = keyring_macos_open_keychain(ckeyring); # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" status = SecKeychainDelete(keychain); # pragma GCC diagnostic pop CFRelease(keychain); keyring_macos_handle_status("cannot delete keyring", status); return R_NilValue; } SEXP keyring_macos_lock_keyring(SEXP keyring) { SecKeychainRef keychain = isNull(keyring) ? NULL : keyring_macos_open_keychain(CHAR(STRING_ELT(keyring, 0))); # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" OSStatus status = SecKeychainLock(keychain); # pragma GCC diagnostic pop if (keychain) CFRelease(keychain); keyring_macos_handle_status("cannot lock keychain", status); return R_NilValue; } SEXP keyring_macos_unlock_keyring(SEXP keyring, SEXP password) { const char *cpassword = CHAR(STRING_ELT(password, 0)); SecKeychainRef keychain = isNull(keyring) ? NULL : keyring_macos_open_keychain(CHAR(STRING_ELT(keyring, 0))); # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" OSStatus status = SecKeychainUnlock( keychain, (UInt32) strlen(cpassword), (const void*) cpassword, /* usePassword = */ TRUE); # pragma GCC diagnostic pop if (keychain) CFRelease(keychain); keyring_macos_handle_status("cannot unlock keychain", status); return R_NilValue; } SEXP keyring_macos_is_locked_keyring(SEXP keyring) { SecKeychainRef keychain = isNull(keyring) ? NULL : keyring_macos_open_keychain(CHAR(STRING_ELT(keyring, 0))); SecKeychainStatus kstatus; OSStatus status = SecKeychainGetStatus(keychain, &kstatus); if (status) keyring_macos_error("cannot get lock information", status); return ScalarLogical(! (kstatus & kSecUnlockStateStatus)); } #else #include #include #include SEXP keyring_macos_get(SEXP keyring, SEXP service, SEXP username) { error("only works on macOS"); return R_NilValue; } SEXP keyring_macos_set(SEXP keyring, SEXP service, SEXP username, SEXP password) { error("only works on macOS"); return R_NilValue; } SEXP keyring_macos_delete(SEXP keyring, SEXP service, SEXP username) { error("only works on macOS"); return R_NilValue; } SEXP keyring_macos_list(SEXP keyring, SEXP service) { error("only works on macOS"); return R_NilValue; } SEXP keyring_macos_create(SEXP keyring, SEXP password) { error("only works on macOS"); return R_NilValue; } SEXP keyring_macos_list_keyring() { error("only works on macOS"); return R_NilValue; } SEXP keyring_macos_delete_keyring(SEXP keyring) { error("only works on macOS"); return R_NilValue; } SEXP keyring_macos_lock_keyring(SEXP keyring) { error("only works on macOS"); return R_NilValue; } SEXP keyring_macos_unlock_keyring(SEXP keyring, SEXP password) { error("only works on macOS"); return R_NilValue; } SEXP keyring_macos_is_locked_keyring(SEXP keyring) { error("only works on macOS"); return R_NilValue; } #endif // __APPLE__ keyring/src/Makevars.win0000644000176200001440000000000014146771774015011 0ustar liggesuserskeyring/src/Makevars.in0000644000176200001440000000000114146771774014623 0ustar liggesusers keyring/src/keyring_wincred.c0000644000176200001440000001031114146771774016056 0ustar liggesusers /* Avoid warning about empty compilation unit. */ void keyring_wincred_dummy() { } #ifdef _WIN32 #include #include #include #include #include #include void keyring_wincred_handle_status(const char *func, BOOL status) { if (status == FALSE) { DWORD errorcode = GetLastError(); LPVOID lpMsgBuf; char *msg; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ); msg = R_alloc(1, strlen(lpMsgBuf) + 1); strcpy(msg, lpMsgBuf); LocalFree(lpMsgBuf); error("Windows credential store error in '%s': %s", func, msg); } } SEXP keyring_wincred_get(SEXP target) { const char *ctarget = CHAR(STRING_ELT(target, 0)); CREDENTIAL *cred; BOOL status = CredRead(ctarget, CRED_TYPE_GENERIC, /* Flags = */ 0, &cred); keyring_wincred_handle_status("get", status); SEXP result = PROTECT(allocVector(RAWSXP, cred->CredentialBlobSize)); memcpy(RAW(result), cred->CredentialBlob, cred->CredentialBlobSize); CredFree(cred); UNPROTECT(1); return result; } SEXP keyring_wincred_exists(SEXP target) { const char *ctarget = CHAR(STRING_ELT(target, 0)); CREDENTIAL *cred = NULL; BOOL status = CredRead(ctarget, CRED_TYPE_GENERIC, /* Flags = */ 0, &cred); DWORD errorcode = status ? 0 : GetLastError(); if (errorcode != 0 && errorcode != ERROR_NOT_FOUND) { keyring_wincred_handle_status("exists", status); } if (cred) CredFree(cred); return ScalarLogical(errorcode == 0); } SEXP keyring_wincred_set(SEXP target, SEXP password, SEXP username, SEXP session) { const char *ctarget = CHAR(STRING_ELT(target, 0)); const char *cusername = isNull(username) ? NULL : CHAR(STRING_ELT(username, 0)); int csession = LOGICAL(session)[0]; CREDENTIAL cred = { 0 }; BOOL status; cred.Type = CRED_TYPE_GENERIC; cred.TargetName = (char*) ctarget; cred.CredentialBlobSize = LENGTH(password); cred.CredentialBlob = (LPBYTE) RAW(password); cred.Persist = csession ? CRED_PERSIST_SESSION : CRED_PERSIST_LOCAL_MACHINE; cred.UserName = (char*) cusername; status = CredWrite(&cred, /* Flags = */ 0); keyring_wincred_handle_status("set", status); return R_NilValue; } SEXP keyring_wincred_delete(SEXP target) { const char* ctarget = CHAR(STRING_ELT(target, 0)); BOOL status = CredDelete(ctarget, CRED_TYPE_GENERIC, /* Flags = */ 0); keyring_wincred_handle_status("delete", status); return R_NilValue; } SEXP keyring_wincred_enumerate(SEXP filter) { const char *cfilter = CHAR(STRING_ELT(filter, 0)); DWORD count; PCREDENTIAL *creds = NULL; BOOL status = CredEnumerate(cfilter, /* Flags = */ 0, &count, &creds); DWORD errorcode = status ? 0 : GetLastError(); /* If there are no keys, then an error is thrown. But for us this is a normal result, and we just return an empty table. */ if (status == FALSE && errorcode == ERROR_NOT_FOUND) { SEXP result = PROTECT(allocVector(STRSXP, 0)); UNPROTECT(1); return result; } else if (status == FALSE) { if (creds != NULL) CredFree(creds); keyring_wincred_handle_status("list", status); return R_NilValue; } else { size_t i, num = (size_t) count; SEXP result = PROTECT(allocVector(STRSXP, num)); for (i = 0; i < count; i++) { SET_STRING_ELT(result, i, mkChar(creds[i]->TargetName)); } CredFree(creds); UNPROTECT(1); return result; } } #else #include #include #include SEXP keyring_wincred_get(SEXP target) { error("only works on Windows"); return R_NilValue; } SEXP keyring_wincred_exists(SEXP target) { error("only works on Windows"); return R_NilValue; } SEXP keyring_wincred_set(SEXP target, SEXP password, SEXP username, SEXP session) { error("only works on Windows"); return R_NilValue; } SEXP keyring_wincred_delete(SEXP target) { error("only works on Windows"); return R_NilValue; } SEXP keyring_wincred_enumerate(SEXP filter) { error("only works on Windows"); return R_NilValue; } #endif // _WIN32 keyring/configure.win0000755000176200001440000000000114146771774014436 0ustar liggesusers keyring/R/0000755000176200001440000000000014150761334012127 5ustar liggesuserskeyring/R/backend-secret-service.R0000644000176200001440000001676314150761257016603 0ustar liggesusers #' Linux Secret Service keyring backend #' #' This backend is the default on Linux. It uses the libsecret library, #' and needs a secret service daemon running (e.g. Gnome Keyring, or #' KWallet). It uses DBUS to communicate with the secret service daemon. #' #' This backend supports multiple keyrings. #' #' See [backend] for the documentation of the individual methods. #' The `is_available()` method checks is a Secret Service daemon is #' running on the system, by trying to connect to it. It returns a logical #' scalar, or throws an error, depending on its argument: #' ``` #' is_available = function(report_error = FALSE) #' ``` #' #' Argument: #' * `report_error` Whether to throw an error if the Secret Service is #' not available. #' #' @family keyring backends #' @export #' @include backend-class.R #' @examples #' \dontrun{ #' ## This only works on Linux, typically desktop Linux #' kb <- backend_secret_service$new() #' kb$keyring_create("foobar") #' kb$set_default_keyring("foobar") #' kb$set_with_value("service", password = "secret") #' kb$get("service") #' kb$delete("service") #' kb$delete_keyring("foobar") #' } backend_secret_service <- R6Class( "backend_secret_service", inherit = backend_keyrings, public = list( name = "secret service", initialize = function(keyring = NULL) b_ss_init(self, private, keyring), get = function(service, username = NULL, keyring = NULL) b_ss_get(self, private, service, username, keyring), get_raw = function(service, username = NULL, keyring = NULL) b_ss_get_raw(self, private, service, username, keyring), set = function(service, username = NULL, keyring = NULL, prompt = "Password: ") b_ss_set(self, private, service, username, keyring, prompt), set_with_value = function(service, username = NULL, password = NULL, keyring = NULL) b_ss_set_with_value(self, private, service, username, password, keyring), set_with_raw_value = function(service, username = NULL, password = NULL, keyring = NULL) b_ss_set_with_raw_value(self, private, service, username, password, keyring), delete = function(service, username = NULL, keyring = NULL) b_ss_delete(self, private, service, username, keyring), list = function(service = NULL, keyring = NULL) b_ss_list(self, private, service, keyring), keyring_create = function(keyring, password = NULL) b_ss_keyring_create(self, private, keyring, password), keyring_list = function() b_ss_keyring_list(self, private), keyring_delete = function(keyring = NULL) b_ss_keyring_delete(self, private, keyring), keyring_lock = function(keyring = NULL) b_ss_keyring_lock(self, private, keyring), keyring_unlock = function(keyring = NULL, password = NULL) b_ss_keyring_unlock(self, private, keyring, password), keyring_is_locked = function(keyring = NULL) b_ss_keyring_is_locked(self, private, keyring), keyring_default = function() b_ss_keyring_default(self, private), keyring_set_default = function(keyring = NULL) b_ss_keyring_set_default(self, private, keyring), is_available = function(report_error = FALSE) b_ss_is_available(self, private, report_error), docs = function() { modifyList(super$docs(), list( . = "Store secrets using the Secret Service library and daemon.", is_available = "check is Secret Service is available" )) } ), private = list( keyring = NULL, keyring_create_direct = function(keyring, password = NULL) b_ss_keyring_create_direct(self, private, keyring, password) ) ) b_ss_init <- function(self, private, keyring) { private$keyring <- keyring invisible(self) } b_ss_get <- function(self, private, service, username, keyring) { res <- b_ss_get_raw(self, private, service, username, keyring) if (any(res == 0)) { stop("Key contains embedded null bytes, use get_raw()") } rawToChar(res) } b_ss_get_raw <- function(self, private, service, username, keyring) { username <- username %||% getOption("keyring_username") keyring <- keyring %||% private$keyring res <- .Call(keyring_secret_service_get, keyring, service, username) res } b_ss_set <- function(self, private, service, username, keyring, prompt) { username <- username %||% getOption("keyring_username") password <- get_pass(prompt) if (is.null(password)) stop("Aborted setting keyring key") b_ss_set_with_value(self, private, service, username, password, keyring) invisible(self) } b_ss_set_with_value <- function(self, private, service, username, password, keyring) { username <- username %||% getOption("keyring_username") keyring <- keyring %||% private$keyring .Call(keyring_secret_service_set, keyring, service, username, charToRaw(password)) invisible(self) } b_ss_set_with_raw_value <- function(self, private, service, username, password, keyring) { username <- username %||% getOption("keyring_username") keyring <- keyring %||% private$keyring .Call(keyring_secret_service_set, keyring, service, username, password) invisible(self) } b_ss_delete <- function(self, private, service, username, keyring) { username <- username %||% getOption("keyring_username") keyring <- keyring %||% private$keyring .Call(keyring_secret_service_delete, keyring, service, username) invisible(self) } b_ss_list <- function(self, private, service, keyring) { keyring <- keyring %||% private$keyring res <- .Call(keyring_secret_service_list, keyring, service) data.frame( service = res[[1]], username = res[[2]], stringsAsFactors = FALSE ) } b_ss_keyring_create <- function(self, private, keyring, password) { password <- password %||% get_pass() if (is.null(password)) stop("Aborted creating keyring") private$keyring_create_direct(keyring, password) invisible(self) } b_ss_keyring_list <- function(self, private) { res <- .Call(keyring_secret_service_list_keyring) data.frame( keyring = res[[1]], num_secrets = res[[2]], locked = res[[3]], stringsAsFactors = FALSE ) } b_ss_keyring_delete <- function(self, private, keyring) { self$confirm_delete_keyring(keyring) keyring <- keyring %||% private$keyring .Call(keyring_secret_service_delete_keyring, keyring) invisible() } b_ss_keyring_lock <- function(self, private, keyring) { keyring <- keyring %||% private$keyring .Call(keyring_secret_service_lock_keyring, keyring) invisible() } b_ss_keyring_unlock <- function(self, private, keyring, password) { keyring <- keyring %||% private$keyring if (! is.null(password)) warning("password ignored, will be read interactively") .Call(keyring_secret_service_unlock_keyring, keyring, password) invisible() } b_ss_keyring_is_locked <- function(self, private, keyring) { keyring <- keyring %||% private$keyring .Call(keyring_secret_service_is_locked_keyring, keyring) } b_ss_keyring_default <- function(self, private) { private$keyring } b_ss_keyring_set_default <- function(self, private, keyring) { private$keyring <- keyring invisible(self) } b_ss_is_available <- function(self, private, report_error) { .Call(keyring_secret_service_is_available, report_error) } b_ss_keyring_create_direct <- function(self, private, keyring, password) { if (!is.null(password)) { warning("Password ignored, will be read interactively") } keyring <- keyring %||% private$keyring .Call(keyring_secret_service_create_keyring, keyring) invisible(self) } keyring/R/pass.R0000644000176200001440000000012414146771774013233 0ustar liggesusersget_pass <- function(prompt = "Password: ") { askpass::askpass(prompt = prompt) } keyring/R/utils.R0000644000176200001440000000401414146771774013427 0ustar liggesusers utf8 <- function(x) { if (is.null(x)) return(x) iconv(x, "", "UTF-8") } `%||%` <- function(l, r) if (is.null(l)) r else l cat0 <- function(..., sep = "") { cat(..., sep = sep) } confirmation <- function(prompt, yes) { ans <- readline(paste0(prompt, ": ")) if (ans != yes) stop("Aborted", call. = FALSE) } darwin_version <- function() { info <- Sys.info() if (info[["sysname"]] != "Darwin") stop("Not macOS") package_version(info[["release"]]) } file_stamp <- function(x) { as.character(tools::md5sum(x)) } str_starts_with <- function(x, p) { ncp <- nchar(p) substr(x, 1, nchar(p)) == p } URLencode <- function(URL) { good <- c(LETTERS, letters, 0:9, ".", "_", "~", "-") x <- strsplit(URL, "")[[1L]] bad <- which(! x %in% good) tr <- function(x) { paste0("%", toupper(as.character(charToRaw(x))), collapse = "") } if (length(bad)) x[bad] <- vapply(x[bad], tr, character(1)) paste(x, collapse = "") } get_encoding_opt <- function() { chk <- function(x) is.character(x) && length(x) == 1 && !is.na(x) enc <- getOption("keyring.encoding_windows") if (!is.null(enc) && !chk(enc)) { stop("Invalid 'keyring.encoding_windows' option, must be an ", "encoding name or 'auto'") } enc <- enc %||% Sys.getenv("KEYRING_ENCODING_WINDOWS", "auto") # Confirm valid encoding. Suggest closest match if not found. if (enc != "auto" & !(tolower(enc) %in% tolower(iconvlist()))) { icl <- iconvlist() closest <- icl[which.min(utils::adist(enc, icl))] stop(sprintf( "Encoding not found in iconvlist(). Did you mean %s?", closest )) } enc } is_interactive <- function() { opt <- getOption("rlib_interactive") if (isTRUE(opt)) { TRUE } else if (identical(opt, FALSE)) { FALSE } else if (tolower(getOption("knitr.in.progress", "false")) == "true") { FALSE } else if (tolower(getOption("rstudio.notebook.executing", "false")) == "true") { FALSE } else if (identical(Sys.getenv("TESTTHAT"), "true")) { FALSE } else { interactive() } } keyring/R/backend-env.R0000644000176200001440000001015114150747225014430 0ustar liggesusers #' Environment variable keyring backend #' #' This is a simple keyring backend, that stores/uses secrets in #' environment variables of the R session. #' #' It does not support multiple keyrings. It also does not support listing #' all keys, since there is no way to distinguish keys from regular #' environment variables. #' #' It does support service names and usernames: they will be separated #' with a `:` character in the name of the environment variable. (Note that #' such an environment variable typically cannot be set or queried from a #' shell, but it can be set and queried from R or other programming #' languages.) #' #' See [backend] for the documentation of the class's methods. #' #' @family keyring backends #' @export #' @include backend-class.R #' @examples #' \dontrun{ #' env <- backend_env$new() #' env$set("r-keyring-test", username = "donaldduck") #' env$get("r-keyring-test", username = "donaldduck") #' Sys.getenv("r-keyring-test:donaldduck") #' #' # This is an error #' env$list() #' #' # Clean up #' env$delete("r-keyring-test", username = "donaldduck") #' } backend_env <- R6Class( "backend_env", inherit = backend, public = list( name = "env", get = function(service, username = NULL, keyring = NULL) b_env_get(self, private, service, username, keyring), set = function(service, username = NULL, keyring = NULL, prompt = "Password: ") b_env_set(self, private, service, username, keyring, prompt), set_with_value = function(service, username = NULL, password = NULL, keyring = NULL) b_env_set_with_value(self, private, service, username, password, keyring), delete = function(service, username = NULL, keyring = NULL) b_env_delete(self, private, service, username, keyring), list = function(service = NULL, keyring = NULL) b_env_list(self, private, service, keyring), docs = function() { modifyList(super$docs(), list( . = "Store secrets in environment variables." )) } ), private = list( env_to_var = function(service, username) { b_env_to_var(self, private, service, username) } ) ) warn_for_keyring <- function(keyring) { if (!is.null(keyring)) { warning("The 'env' backend does not support multiple keyrings, ", "the 'keyring' argument is ignored") } } b_env_get <- function(self, private, service, username, keyring) { warn_for_keyring(keyring) username <- username %||% getOption("keyring_username") var <- private$env_to_var(service, username) res <- Sys.getenv(var, NA_character_) if (is.na(res)) stop("Cannot find password") res } b_env_set <- function(self, private, service, username, keyring, prompt) { warn_for_keyring(keyring) password <- get_pass(prompt) if (is.null(password)) stop("Aborted setting keyring key") username <- username %||% getOption("keyring_username") b_env_set_with_value(self, private, service, username, password, keyring = NULL) invisible(self) } b_env_set_with_value <- function(self, private, service, username, password, keyring) { warn_for_keyring(keyring) username <- username %||% getOption("keyring_username") var <- private$env_to_var(service, username) do.call(Sys.setenv, structure(list(password), names = var)) invisible(self) } b_env_delete <- function(self, private, service, username, keyring) { warn_for_keyring(keyring) username <- username %||% getOption("keyring_username") var <- private$env_to_var(service, username) Sys.unsetenv(var) invisible(self) } b_env_to_var <- function(self, private, service, username, keyring) { if (is.null(username)) { service } else { paste0(service, ":", username) } } b_env_list <- function(self, private, service, keyring) { if (is.null(service)) stop("'service' is required for 'env' backend.") keys <- gsub( paste(service, ":", sep = ""), "", Filter(function(e) str_starts_with(e, service), names(Sys.getenv())) ) data.frame( service = rep(service, length(keys)), username = keys, stringsAsFactors = FALSE ) } keyring/R/backend-wincred.R0000644000176200001440000004312314150761334015275 0ustar liggesusers ## The windows credential store does not support multiple keyrings, ## so we emulate them. See the inst/development-notes.md file for a ## complete description on how this is done. b_wincred_protocol_version <- "1.0.0" ## This is a low level API b_wincred_i_get <- function(target) { .Call(keyring_wincred_get, target) } b_wincred_i_set <- function(target, password, username = NULL, session = FALSE) { username <- username %||% getOption("keyring_username") .Call(keyring_wincred_set, target, password, username, session) } b_wincred_i_delete <- function(target) { .Call(keyring_wincred_delete, target) } b_wincred_i_exists <- function(target) { .Call(keyring_wincred_exists, target) } b_wincred_i_enumerate <- function(filter) { .Call(keyring_wincred_enumerate, filter) } b_wincred_i_escape <- function(x) { if (is.null(x)) x else URLencode(x) } #' @importFrom utils URLdecode b_wincred_i_unescape <- function(x) { URLdecode(x) } b_wincred_target <- function(keyring, service, username) { username <- username %||% getOption("keyring_username") keyring <- if (is.null(keyring)) "" else b_wincred_i_escape(keyring) service <- b_wincred_i_escape(service) username <- if (is.null(username)) "" else b_wincred_i_escape(username) paste0(keyring, ":", service, ":", username) } ## For the username we need a workaround, because ## strsplit("foo::")[[1]] gives c("foo", ""), i.e. the third empty element ## is cut off. b_wincred_i_parse_target <- function(target) { parts <- lapply(strsplit(target, ":"), lapply, b_wincred_i_unescape) extract <- function(x, i) x[i][[1]] %||% "" res <- data.frame( stringsAsFactors = FALSE, keyring = vapply(parts, extract, "", 1), service = vapply(parts, extract, "", 2), username = vapply(parts, extract, "", 3) ) } b_wincred_target_keyring <- function(keyring) { b_wincred_target(keyring, "", "") } b_wincred_target_lock <- function(keyring) { b_wincred_target(keyring, "", "unlocked") } b_wincred_parse_keyring_credential <- function(target) { value <- rawToChar(b_wincred_i_get(target)) con <- textConnection(value) on.exit(close(con), add = TRUE) as.list(read.dcf(con)[1,]) } b_wincred_write_keyring_credential <- function(target, data) { con <- textConnection(NULL, open = "w") mat <- matrix(unlist(data), nrow = 1) colnames(mat) <- names(data) write.dcf(mat, con) value <- paste0(paste(textConnectionValue(con), collapse = "\n"), "\n") close(con) b_wincred_i_set(target, password = charToRaw(value)) } #' @importFrom utils head tail b_wincred_get_encrypted_aes <- function(str) { r <- openssl::base64_decode(str) structure(tail(r, -16), iv = head(r, 16)) } ## 1. Try to get the unlock credential ## 2. If it exists, return AES key ## 3. If not, then get the credential of the keyring ## 4. Ask for the keyring password ## 5. Hash the password to get the AES key ## 6. Verify that the AES key is correct, using the Verify field of ## the keyring credential ## 7. Create a SESSION credential, with the decrypted AES key ## 8. Return the decrypted AES key b_wincred_unlock_keyring_internal <- function(keyring, password = NULL) { target_lock <- b_wincred_target_lock(keyring) if (b_wincred_i_exists(target_lock)) { openssl::base64_decode(rawToChar(b_wincred_i_get(target_lock))) } else { target_keyring <- b_wincred_target_keyring(keyring) keyring_data <- b_wincred_parse_keyring_credential(target_keyring) if (is.null(password)) { message("keyring ", sQuote(keyring), " is locked, enter password to unlock") password <- get_pass() if (is.null(password)) stop("Aborted unlocking keyring") } aes <- openssl::sha256(charToRaw(password), key = keyring_data$Salt) verify <- b_wincred_get_encrypted_aes(keyring_data$Verify) tryCatch( openssl::aes_cbc_decrypt(verify, key = aes), error = function(e) stop("Invalid password, cannot unlock keyring") ) b_wincred_i_set( target_lock, charToRaw(openssl::base64_encode(aes)), session = TRUE) aes } } b_wincred_is_locked_keyring_internal <- function(keyring) { target_lock <- b_wincred_target_lock(keyring) ! b_wincred_i_exists(target_lock) } ## ----------------------------------------------------------------------- #' Windows Credential Store keyring backend #' #' This backend is the default on Windows. It uses the native Windows #' Credential API, and needs at least Windows XP to run. #' #' This backend supports multiple keyrings. Note that multiple keyrings #' are implemented in the `keyring` R package, using some dummy keyring #' keys that represent keyrings and their locked/unlocked state. #' #' See [backend] for the documentation of the individual methods. #' #' @family keyring backends #' @export #' @examples #' \dontrun{ #' ## This only works on Windows #' kb <- backend_wincred$new() #' kb$keyring_create("foobar") #' kb$set_default_keyring("foobar") #' kb$set_with_value("service", password = "secret") #' kb$get("service") #' kb$delete("service") #' kb$delete_keyring("foobar") #' } backend_wincred <- R6Class( "backend_wincred", inherit = backend_keyrings, public = list( name = "windows credential store", initialize = function(keyring = NULL) b_wincred_init(self, private, keyring), get = function(service, username = NULL, keyring = NULL) b_wincred_get(self, private, service, username, keyring), get_raw = function(service, username = NULL, keyring = NULL) b_wincred_get_raw(self, private, service, username, keyring), set = function(service, username = NULL, keyring = NULL, prompt = "Password: ") b_wincred_set(self, private, service, username, keyring, prompt), set_with_value = function(service, username = NULL, password = NULL, keyring = NULL) b_wincred_set_with_value(self, private, service, username, password, keyring), set_with_raw_value = function(service, username = NULL, password = NULL, keyring = NULL) b_wincred_set_with_value(self, private, service, username, password, keyring), delete = function(service, username = NULL, keyring = NULL) b_wincred_delete(self, private, service, username, keyring), list = function(service = NULL, keyring = NULL) b_wincred_list(self, private, service, keyring), keyring_create = function(keyring, password = NULL) b_wincred_keyring_create(self, private, keyring, password), keyring_list = function() b_wincred_keyring_list(self, private), keyring_delete = function(keyring = NULL) b_wincred_keyring_delete(self, private, keyring), keyring_lock = function(keyring = NULL) b_wincred_keyring_lock(self, private, keyring), keyring_unlock = function(keyring = NULL, password = NULL) b_wincred_keyring_unlock(self, private, keyring, password), keyring_is_locked = function(keyring = NULL) b_wincred_keyring_is_locked(self, private, keyring), keyring_default = function() b_wincred_keyring_default(self, private), keyring_set_default = function(keyring = NULL) b_wincred_keyring_set_default(self, private, keyring), docs = function() { modifyList(super$docs(), list( . = "Store secrets in the Windows Credential Store." )) } ), private = list( keyring = NULL, keyring_create_direct = function(keyring, password) b_wincred_keyring_create_direct(self, private, keyring, password) ) ) b_wincred_init <- function(self, private, keyring) { private$keyring <- keyring invisible(self) } #' Get a key from a Wincred keyring #' #' @param backend Backend object. #' @param service Service name. Must not be empty. #' @param username Username. Might be empty. #' #' 1. We check if the key is on the default keyring. #' 2. If yes, we just return it. #' 3. Otherwise check if the keyring is locked. #' 4. If locked, then unlock it. #' 5. Get the AES key from the keyring. #' 6. Decrypt the key with the AES key. #' #' Additionally, users may specify an encoding to use when converting the #' password from a byte-string, for compatibility with other software such as #' python's keyring package. This is done via an option, or an environment variable. #' #' @keywords internal b_wincred_get <- function(self, private, service, username, keyring) { password <- self$get_raw(service, username, keyring) encoding <- get_encoding_opt() b_wincred_decode(password, encoding = encoding) } b_wincred_get_raw <- function(self, private, service, username, keyring) { keyring <- keyring %||% private$keyring target <- b_wincred_target(keyring, service, username) password <- b_wincred_i_get(target) if (! is.null(keyring)) { ## If it is encrypted, we need to decrypt it aes <- b_wincred_unlock_keyring_internal(keyring) enc <- b_wincred_get_encrypted_aes(rawToChar(password)) password <- openssl::aes_cbc_decrypt(enc, key = aes) } password } #' Decode a raw password obtained by b_wincred_get_raw #' #' Defaults to 'auto' encoding, which uses `b_wincred_decode_auto` to #' accomplish the decoding (this works with decoding either UTF-8 or #' UTF-16LE encodings). In the case where an encoding is specified, #' use that to convert the raw password. #' #' @param password A raw byte string returned from `b_wincred_get_raw`. #' @param encoding A character value, specifying an encoding to use. #' Defaults to 'auto', which will decode either of UTF-8 or UTF-16LE. #' @return A character value containing a password. #' #' @keywords internal b_wincred_decode <- function(password, encoding = 'auto') { if (encoding == 'auto') { b_wincred_decode_auto(password) } else { password <- iconv(list(password), from = encoding, to = "") password } } #' Decode a raw password obtained by b_wincred_get_raw #' (UTF-8 and UTF-16LE only) #' #' It attempts to use UTF-16LE conversion if there are 0 values in #' the password. #' #' @param password Raw vector coming from the keyring. #' #' @keywords internal b_wincred_decode_auto <- function(password) { if (any(password == 0)) { password <- iconv(list(password), from = "UTF-16LE", to = "") if (is.na(password)) { stop("Key contains embedded null bytes, use get_raw()") } password } else { rawToChar(password) } } b_wincred_set <- function(self, private, service, username, keyring, prompt) { password <- get_pass(prompt) if (is.null(password)) stop("Aborted setting keyring key") b_wincred_set_with_value(self, private, service, username, password, keyring) invisible(self) } b_wincred_set_with_value <- function(self, private, service, username, password, keyring) { encoding <- get_encoding_opt() if (encoding != 'auto') { password <- enc2utf8(password) password <- iconv(x = password, from = 'UTF-8', to = encoding, toRaw = TRUE)[[1]] } else { password <- charToRaw(password) } b_wincred_set_with_raw_value(self, private, service, username, password, keyring) } #' Set a key on a Wincred keyring #' #' @param service Service name. Must not be empty. #' @param username Username. Might be empty. #' @param password The key text to store. #' #' 1. Check if we are using the default keyring. #' 2. If yes, then just store the key and we are done. #' 3. Otherwise check if keyring exists. #' 4. If not, error and finish. #' 5. If yes, check if it is locked. #' 6. If yes, unlock it. #' 7. Encrypt the key with the AES key, and store it. #' #' If required, an encoding can be specified using either an R option #' (\code{keyring.encoding_windows}) or environment variable #' (\code{KEYRING_ENCODING_WINDOWS}). To set, use one of: #' #' \code{options(keyring.encoding_windows = 'encoding-type')} #' \code{Sys.setenv("KEYRING_ENCODING_WINDOWS" = 'encoding-type')} #' #' For a list of valid encodings, use \code{iconvlist()} #' #' @keywords internal b_wincred_set_with_raw_value <- function(self, private, service, username, password, keyring) { keyring <- keyring %||% private$keyring target <- b_wincred_target(keyring, service, username) if (is.null(keyring)) { b_wincred_i_set(target, password, username = username) return(invisible(self)) } ## Not the default keyring, we need to encrypt it target_keyring <- b_wincred_target_keyring(keyring) aes <- b_wincred_unlock_keyring_internal(keyring) enc <- openssl::aes_cbc_encrypt(password, key = aes) password <- charToRaw(openssl::base64_encode(c(attr(enc, "iv"), enc))) b_wincred_i_set(target, password = password, username = username) invisible(self) } b_wincred_delete <- function(self, private, service, username, keyring) { keyring <- keyring %||% private$keyring target <- b_wincred_target(keyring, service, username) b_wincred_i_delete(target) invisible(self) } b_wincred_list <- function(self, private, service, keyring) { keyring <- keyring %||% private$keyring filter <- if (is.null(service)) { paste0(b_wincred_i_escape(keyring), ":*") } else { paste0(b_wincred_i_escape(keyring), ":", b_wincred_i_escape(service), ":*") } list <- b_wincred_i_enumerate(filter) ## Filter out the credentials that belong to the keyring or its lock list <- grep("(::|::unlocked)$", list, value = TRUE, invert = TRUE) parts <- b_wincred_i_parse_target(list) data.frame( service = parts$service, username = parts$username, stringsAsFactors = FALSE ) } b_wincred_keyring_create <- function(self, private, keyring, password) { password <- password %||% get_pass() if (is.null(password)) stop("Aborted craeting keyring") private$keyring_create_direct(keyring, password) invisible(self) } ## 1. Check that the keyring does not exist, error if it does ## 2. Create salt. ## 3. SHA256 hash the password, with the salt, to get the AES key. ## 4. Generate 15 random bytes, encrypt it with the AES key, base64 encode it. ## 5. Write metadata to the keyring credential ## 6. Unlock the keyring immediately, create a keyring unlock credential b_wincred_keyring_create_direct <- function(self, private, keyring, password) { target_keyring <- b_wincred_target_keyring(keyring) if (b_wincred_i_exists(target_keyring)) { stop("keyring ", sQuote(keyring), " already exists") } salt <- openssl::base64_encode(openssl::rand_bytes(32)) aes <- openssl::sha256(charToRaw(password), key = salt) verify <- openssl::aes_cbc_encrypt(openssl::rand_bytes(15), key = aes) verify <- openssl::base64_encode(c(attr(verify, "iv"), verify)) dcf <- list( Version = b_wincred_protocol_version, Verify = verify, Salt = salt ) b_wincred_write_keyring_credential(target_keyring, dcf) b_wincred_unlock_keyring_internal(keyring, password) invisible(self) } b_wincred_keyring_list <- function(self, private) { list <- b_wincred_i_enumerate("*") parts <- b_wincred_i_parse_target(list) ## if keyring:: does not exist, then keyring is not a real keyring, assign it ## to the default default <- ! paste0(parts$keyring, "::") %in% list if (length(list) > 0 && any(default)) { parts$username[default] <- paste0(parts$service[default], ":", parts$username[default]) parts$service[default] <- parts$keyring[default] parts$keyring[default] <- "" } res <- data.frame( stringsAsFactors = FALSE, keyring = unname(unique(parts$keyring)), num_secrets = as.integer(unlist(tapply(parts$keyring, factor(parts$keyring, levels = unique(parts$keyring)), length, simplify = FALSE))), locked = vapply(unique(parts$keyring), FUN.VALUE = TRUE, USE.NAMES = FALSE, function(x) { ! any(parts$username[parts$keyring == x] == "unlocked") } ) ) ## Subtract keyring::unlocked and also keyring:: for the non-default keyring res$num_secrets <- res$num_secrets - (! res$locked) - (res$keyring != "") ## The default keyring cannot be locked if ("" %in% res$keyring) res$locked[res$keyring == ""] <- FALSE res } b_wincred_keyring_delete <- function(self, private, keyring) { self$confirm_delete_keyring(keyring) keyring <- keyring %||% private$keyring items <- self$list(keyring = keyring) ## Remove the keyring credential and the lock credential first target_keyring <- b_wincred_target_keyring(keyring) b_wincred_i_delete(target_keyring) target_lock <- b_wincred_target_lock(keyring) try(b_wincred_i_delete(target_lock), silent = TRUE) ## Then the items themselves for (i in seq_len(nrow(items))) { target <- b_wincred_target(keyring, items$service[i], items$username[i]) try(b_wincred_i_delete(target), silent = TRUE) } invisible() } b_wincred_keyring_lock <- function(self, private, keyring) { keyring <- keyring %||% private$keyring if (is.null(keyring)) { warning("Cannot lock the default windows credential store keyring") } else { target_lock <- b_wincred_target_lock(keyring) try(b_wincred_i_delete(target_lock), silent = TRUE) invisible() } } b_wincred_keyring_unlock <- function(self, private, keyring, password = NULL) { keyring <- keyring %||% private$keyring if (is.null(password)) password <- get_pass() if (is.null(password)) stop("Aborted unlocking keyring") if (!is.null(keyring)) { b_wincred_unlock_keyring_internal(keyring, password) } invisible() } b_wincred_keyring_is_locked <- function(self, private, keyring) { keyring <- keyring %||% private$keyring if (is.null(keyring)) { FALSE } else { b_wincred_is_locked_keyring_internal(keyring) } } b_wincred_keyring_default <- function(self, private) { private$keyring } b_wincred_keyring_set_default <- function(self, private, keyring) { private$keyring <- keyring invisible(self) } keyring/R/backend-class.R0000644000176200001440000001717114150761125014751 0ustar liggesusers abstract_method <- function() { stop("An abstract keyring method is called. This is an internal error. ", "It most likely happends because of a broken keyring backend that ", "does not implement all keyring functions.") } #' Abstract class of a minimal keyring backend #' #' To implement a new keyring backend, you need to inherit from this #' class and then redefine the `get`, `set`, `set_with_value` and `delete` #' methods. Implementing the `list` method is optional. Additional methods #' can be defined as well. #' #' These are the semantics of the various methods: #' #' ``` #' get(service, username = NULL, keyring = NULL) #' get_raw(service, username = NULL, keyring = NULL) #' set(service, username = NULL, keyring = NULL, prompt = "Password: ") #' set_with_value(service, username = NULL, password = NULL, #' keyring = NULL) #' set_with_raw_value(service, username = NULL, password = NULL, #' keyring = NULL) #' delete(service, username = NULL, keyring = NULL) #' list(service = NULL, keyring = NULL) #' ``` #' #' What these functions do: #' #' * `get()` queries the secret in a keyring item. #' * `get_raw()` is similar to `get()`, but returns the result as a raw #' vector. #' * `set()` sets the secret in a keyring item. The secret itself is read #' in interactively from the keyboard. #' * `set_with_value()` sets the secret in a keyring item to the specified #' value. #' * `set_with_raw_value()` sets the secret in keyring item to the #' byte sequence of a raw vector. #' * `delete()` remotes a keyring item. #' * `list()` lists keyring items. #' #' The arguments: #' * `service` String, the name of a service. This is used to find the #' secret later. #' * `username` String, the username associated with a secret. It can be #' `NULL`, if no username belongs to the secret. It uses the value of #' the `keyring_username`, if set. #' * `keyring` String, the name of the keyring to work with. This only makes #' sense if the platform supports multiple keyrings. `NULL` selects the #' default (and maybe only) keyring. #' * `password` The value of the secret, typically a password, or other #' credential. #' * `prompt` String, the text to be displayed above the textbox. #' #' @family keyring backend base classes #' @importFrom R6 R6Class #' @name backend NULL #' @export backend <- R6Class( "backend", public = list( name = "abstract backend", has_keyring_support = function() FALSE, get = function(service, username = NULL, keyring = NULL) abstract_method(), get_raw = function(service, username = NULL, keyring = NULL) charToRaw(self$get(service, username, keyring)), set = function(service, username = NULL, keyring = NULL, prompt = "Password: ") abstract_method(), set_with_value = function(service, username = NULL, password = NULL, keyring = NULL) abstract_method(), set_with_raw_value = function(service, username = NULL, password = NULL, keyring = NULL) self$set_with_value(service, username, rawToChar(password), keyring), delete = function(service, username = NULL, keyring = NULL) abstract_method(), list = function(service = NULL, keyring = NULL) stop("Backend does not implement 'list'"), print = function(...) { d <- self$docs() cat0("\n") cat0(d[[1]], "\n\n") cat0(paste0(" $", format(names(d[-1])), " ", d[-1]), sep = "\n") invisible(self) }, ## This should be 'protected', really, but not possible in R6 confirm_delete_keyring = function(keyring) { if (is.null(keyring)) { stop("Cannot delete the default keyring. ", "You need to specify the name of the keyring explicitly.") } list <- self$keyring_list() if (keyring %in% list$keyring && list$num_secrets[match(keyring, list$keyring)] > 0) { confirmation( "The keyring is not empty, type 'yes' to delete it", "yes" ) } }, docs = function() { list( . = "Inherit from this class to implement a basic backend.", get = "query a key from the keyring", set = "set a key in the keyring (interactive)", set_with_value = "set a key in the keyring", delete = "delete a key", list = "list keys in a keyring", has_keyring_support = "TRUE if multiple keyrings are supported" ) } ) ) #' Abstract class of a backend that supports multiple keyrings #' #' To implement a new keyring that supports multiple keyrings, you need to #' inherit from this class and redefine the `get`, `set`, `set_with_value`, #' `delete`, `list` methods, and also the keyring management methods: #' `keyring_create`, `keyring_list`, `keyring_delete`, `keyring_lock`, #' `keyring_unlock`, `keyring_is_locked`, `keyring_default` and #' `keyring_set_default`. #' #' See [backend] for the first set of methods. This is the semantics of the #' keyring management methods: #' #' ``` #' keyring_create(keyring) #' keyring_list() #' keyring_delete(keyring = NULL) #' keyring_lock(keyring = NULL) #' keyring_unlock(keyring = NULL, password = NULL) #' keyring_is_locked(keyring = NULL) #' keyring_default() #' keyring_set_default(keyring = NULL) #' ``` #' #' * `keyring_create()` creates a new keyring. #' * `keyring_list()` lists all keyrings. #' * `keyring_delete()` deletes a keyring. It is a good idea to protect #' the default keyring, and/or a non-empty keyring with a password or #' a confirmation dialog. #' * `keyring_lock()` locks a keyring. #' * `keyring_unlock()` unlocks a keyring. #' * `keyring_is_locked()` checks whether a keyring is locked. #' * `keyring_default()` returns the default keyring. #' * `keyring_set_default()` sets the default keyring. #' #' Arguments: #' * `keyring` is the name of the keyring to use or create. For some #' methods in can be `NULL` to select the default keyring. #' * `password` is the password of the keyring. #' #' @family keyring backend base classes #' @name backend_keyrings NULL #' @export backend_keyrings <- R6Class( "backend_keyrings", inherit = backend, public = list( has_keyring_support = function() TRUE, get = function(service, username = NULL, keyring = NULL) abstract_method(), set = function(service, username = NULL, keyring = NULL) abstract_method(), set_with_value = function(service, username = NULL, password = NULL, keyring = NULL) abstract_method(), delete = function(service, username = NULL) abstract_method(), list = function(service = NULL, keyring = NULL) abstract_method(), keyring_create = function(keyring, password) abstract_method(), keyring_list = function() abstract_method(), keyring_delete = function(keyring = NULL) abstract_method(), keyring_lock = function(keyring = NULL) abstract_method(), keyring_unlock = function(keyring = NULL, password = NULL) abstract_method(), keyring_is_locked = function(keyring = NULL) abstract_method(), keyring_default = function() abstract_method(), keyring_set_default = function(keyring = NULL) abstract_method(), docs = function() { modifyList(super$docs(), list( . = "Inherit from this class for a new backend with multiple keyrings.", keyring_create = "create new keyring", keyring_list = "list all keyrings", keyring_delete = "delete a keyring", keyring_lock = "lock a keyring", keyring_unlock = "unlock a keyring", keyring_is_locked = "check if a keyring is locked", keyring_default = "query the default keyring", keyring_set_default = "set the default keyring" )) } ) ) keyring/R/default_backend.R0000644000176200001440000000754114146771774015372 0ustar liggesusers #' Select the default backend and default keyring #' #' The default backend is selected #' 1. based on the `keyring_backend` option. See [base::options()]. #' This can be set to a character string, and then the #' *backend_*`string` class is used to create the default backend. #' 1. If this is not set, then the `R_KEYRING_BACKEND` environment variable #' is checked. #' 1. If this is not set, either, then the backend is selected #' automatically, based on the OS: #' 1. On Windows, the Windows Credential Store (`"wincred"`) is used. #' 1. On macOS, Keychain services are selected (`"macos"`). #' 1. Linux uses the Secret Service API (`"secret_service"`), #' and it also checks that the service is available. It is typically #' only available on systems with a GUI. #' 1. If the file backend (`"file"`) is available, it is selected. #' 1. On other operating systems, secrets are stored in environment #' variables (`"env"`). #' #' Most backends support multiple keyrings. For these the keyring is #' selected from: #' 1. the supplied `keyring` argument (if not `NULL`), or #' 1. the `keyring_keyring` option. #' - You can change this by using `options(keyring_keyring = "NEWVALUE")` #' 1. If this is not set, the `R_KEYRING_KEYRING` environment variable. #' - Change this value with `Sys.setenv(R_KEYRING_KEYRING = "NEWVALUE")`, #' either in your script or in your `.Renviron` file. #' See [base::Startup] for information about using `.Renviron` #' 1. Finally, if neither of these are set, the OS default keyring is used. #' - Usually the keyring is automatically unlocked when the user logs in. #' #' @param keyring Character string, the name of the keyring to use, #' or `NULL` for the default keyring. #' @return The backend object itself. #' #' #' @seealso [backend_env], [backend_file], [backend_macos], #' [backend_secret_service], [backend_wincred] #' #' @export #' @name backends default_backend <- function(keyring = NULL) { assert_that(is_string_or_null(keyring)) backend <- getOption("keyring_backend", "") if (identical(backend, "")) backend <- default_backend_env_or_auto() ## Is it just a backend name? if (is_string(backend)) backend <- backend_factory(backend) ## At this point 'backend' is a backend R6 class ## Check if a specific keyring is requested if (is.null(keyring)) { keyring <- getOption( "keyring_keyring", Sys.getenv("R_KEYRING_KEYRING", "") ) } if (! is.null(keyring) && nzchar(keyring)) { backend$new(keyring = keyring) } else { backend$new() } } default_backend_env_or_auto <- function() { backend <- Sys.getenv("R_KEYRING_BACKEND", "") if (identical(backend, "")) backend <- default_backend_auto() backend } default_backend_auto <- function() { sysname <- tolower(Sys.info()[["sysname"]]) if (sysname == "windows" && "wincred" %in% names(known_backends)) { backend_wincred } else if (sysname == "darwin" && "macos" %in% names(known_backends)) { backend_macos } else if (sysname == "linux" && "secret_service" %in% names(known_backends) && backend_secret_service$new()$is_available()) { backend_secret_service } else if ("file" %in% names(known_backends)) { backend_file } else { if (getOption("keyring_warn_for_env_fallback", TRUE)) { warning("Selecting ", sQuote("env"), " backend. ", "Secrets are stored in environment variables") } backend_env } } backend_factory <- function(name) { assert_that(is_string(name)) if (!name %in% names(known_backends)) { stop("Unknown backend: ", sQuote(name)) } class_name <- paste0("backend_", name) get(class_name, envir = parent.frame()) } known_backends <- list( "wincred" = backend_wincred, "macos" = backend_macos, "secret_service" = backend_secret_service, "env" = backend_env, "file" = backend_file ) keyring/R/assertions.R0000644000176200001440000000300314146771774014456 0ustar liggesusers #' @importFrom assertthat on_failure<- assert_that has_name is_string <- function(x) { is.character(x) && length(x) == 1 && !is.na(x) } on_failure(is_string) <- function(call, env) { paste0(deparse(call$x), " is not a string (length 1 character)") } is_string_or_null <- function(x) { is.null(x) || is_string(x) } on_failure(is_string_or_null) <- function(call, env) { paste0(deparse(call$x), " must be a string (length 1 character) or NULL") } is_non_empty_string <- function(x) { is_string(x) && x != "" } on_failure(is_non_empty_string) <- function(call, env) { paste0(deparse(call$x), " must be a non-empty string (length 1 character)") } is_non_empty_string_or_null <- function(x) { is.null(x) || is_non_empty_string(x) } on_failure(is_non_empty_string_or_null) <- function(call, env) { paste0( deparse(call$x), " must be a non-empty string (length 1 character) or NULL" ) } is_string_or_raw <- function(x) { is.raw(x) || is_string(x) } on_failure(is_string_or_raw) <- function(call, env) { paste0( deparse(call$x), " must be a string (length 1 character) or raw vector" ) } is_list_with_names <- function(x, names) { is.list(x) && length(x) == length(names) && all(vapply(names, function(name) has_name(x, name), logical(1L))) } on_failure(is_list_with_names) <- function(call, env) { paste0( deparse(call$x), " must be a named list of length ", length(call$names), " with entries ", paste(vapply(call$names, sQuote, character(1L)), collapse = ", ") ) } keyring/R/backend-file.R0000644000176200001440000004561114150755262014570 0ustar liggesusers b_file_keyrings <- new.env(parent = emptyenv()) #' Encrypted file keyring backend #' #' This is a simple keyring backend, that stores/uses secrets in encrypted #' files. #' #' It supports multiple keyrings. #' #' See [backend] for the documentation of the individual methods. #' #' @family keyring backends #' @export #' @include backend-class.R #' @examples #' \dontrun{ #' kb <- backend_file$new() #' } backend_file <- R6Class( "backend_file", inherit = backend_keyrings, public = list( name = "file", initialize = function(keyring = NULL) b_file_init(self, private, keyring), get = function(service, username = NULL, keyring = NULL) b_file_get(self, private, service, username, keyring), get_raw = function(service, username = NULL, keyring = NULL) b_file_get_raw(self, private, service, username, keyring), set = function(service, username = NULL, keyring = NULL, prompt = NULL) b_file_set(self, private, service, username, keyring, prompt), set_with_value = function(service, username = NULL, password = NULL, keyring = NULL) b_file_set_with_value(self, private, service, username, password, keyring), delete = function(service, username = NULL, keyring = NULL) b_file_delete(self, private, service, username, keyring), list = function(service = NULL, keyring = NULL) b_file_list(self, private, service, keyring), keyring_create = function(keyring = NULL, password = NULL) b_file_keyring_create(self, private, keyring, password), keyring_delete = function(keyring = NULL) b_file_keyring_delete(self, private, keyring), keyring_lock = function(keyring = NULL) b_file_keyring_lock(self, private, keyring), keyring_unlock = function(keyring = NULL, password = NULL) b_file_keyring_unlock(self, private, keyring, password), keyring_is_locked = function(keyring = NULL) b_file_keyring_is_locked(self, private, keyring), keyring_list = function() b_file_keyring_list(self, private), keyring_default = function() b_file_keyring_default(self, private), keyring_set_default = function(keyring) b_file_keyring_set_default(self, private, keyring), docs = function() { modifyList(super$docs(), list(. = paste0( "Store secrets in encrypted files.\n", private$keyring))) } ), private = list( keyring = NULL, keyring_create_direct = function(keyring = NULL, password = NULL, prompt = NULL) b__file_keyring_create_direct(self, private, keyring, password, prompt), keyring_autocreate = function(keyring = NULL) b__file_keyring_autocreate(self, private, keyring), keyring_file = function(keyring = NULL) b__file_keyring_file(self, private, keyring), keyring_read_file = function(keyring = NULL) b__file_keyring_read_file(self, private, keyring), keyring_write_file = function(keyring = NULL, nonce = NULL, items = NULL, key = NULL) b__file_keyring_write_file(self, private, keyring, nonce, items, key), get_keyring_pass = function(keyring = NULL) b__file_get_keyring_pass(self, private, keyring), set_keyring_pass = function(key = NULL, keyring = NULL) b__file_set_keyring_pass(self, private, key, keyring), unset_keyring_pass = function(keyring = NULL) b__file_unset_keyring_pass(self, private, keyring), is_set_keyring_pass = function(keyring = NULL) b__file_is_set_keyring_pass(self, private, keyring), update_cache = function(keyring = NULL, nonce = NULL, check = NULL, items = NULL) b__file_update_cache(self, private, keyring, nonce, check, items), get_cache = function(keyring = NULL) b__file_get_cache(self, private, keyring) ) ) b_file_init <- function(self, private, keyring) { self$keyring_set_default(keyring %||% "system") invisible(self) } b_file_get <- function(self, private, service, username, keyring) { private$keyring_autocreate(keyring) username <- username %||% getOption("keyring_username") if (self$keyring_is_locked(keyring)) self$keyring_unlock(keyring) cached <- private$get_cache(keyring) all_items <- cached$items all_services <- vapply(all_items, `[[`, character(1L), "service_name") item_matches <- all_services %in% service if (!is.null(username)) { all_users <- vapply(all_items, function(x) x$user_name %||% NA_character_, character(1L)) item_matches <- item_matches & all_users %in% username } if (sum(item_matches) < 1L) { b_file_error("cannot get secret", "The specified item could not be found in the keychain.") } vapply( lapply(all_items[item_matches], `[[`, "secret"), b_file_secret_decrypt, character(1L), cached$nonce, private$get_keyring_pass(keyring) ) } b_file_set <- function(self, private, service, username, keyring, prompt) { username <- username %||% getOption("keyring_username") keyring <- keyring %||% private$keyring file <- private$keyring_file(keyring) ex <- file.exists(file) # We use a different prompt in this case, to give a heads up prompt <- prompt %||% if (!ex && interactive()) { paste0( "Note: the specified keyring does not exist, you'll have to ", "create it in the next step. Key password: " ) } else { "Password: " } password <- get_pass(prompt) if (is.null(password)) stop("Aborted setting keyring key") private$keyring_autocreate() self$set_with_value(service, username, password, keyring) invisible(self) } b_file_set_with_value <- function(self, private, service, username, password, keyring) { private$keyring_autocreate(keyring) username <- username %||% getOption("keyring_username") if (self$keyring_is_locked(keyring)) self$keyring_unlock(keyring) keyring_file <- private$keyring_file(keyring) kr_env <- b_file_keyring_env(keyring_file) with_lock(keyring_file, { cached <- private$get_cache(keyring) all_items <- cached$items services <- vapply(all_items, `[[`, character(1L), "service_name") users <- vapply(all_items, function(x) x$user_name %||% NA_character_, character(1)) existing <- if (!is.null(username)) { services %in% service & users %in% username } else { services %in% service & is.na(users) } if (length(existing)) all_items <- all_items[!existing] new_item <- list( service_name = service, user_name = username, secret = b_file_secret_encrypt( password, cached$nonce, private$get_keyring_pass(keyring) ) ) items <- c(all_items, list(new_item)) private$keyring_write_file(keyring, items = items) kr_env$stamp <- file_stamp(keyring_file) }) kr_env <- b_file_keyring_env(keyring_file) kr_env$items <- items invisible(self) } b_file_delete <- function(self, private, service, username, keyring) { username <- username %||% getOption("keyring_username") if (self$keyring_is_locked(keyring)) self$keyring_unlock(keyring) keyring_file <- private$keyring_file(keyring) kr_env <- b_file_keyring_env(keyring_file) with_lock(keyring_file, { cached <- private$get_cache(keyring) all_items <- cached$items services <- vapply(all_items, `[[`, character(1L), "service_name") users <- vapply(all_items, function(x) x$user_name %||% NA_character_, character(1)) existing <- if (!is.null(username)) { services %in% service & users %in% username } else { services %in% service & is.na(users) } if (length(existing) == 0) return(invisible(self)) ## Remove items <- all_items[!existing] private$keyring_write_file(keyring, items = items) kr_env$stamp <- file_stamp(keyring_file) }) kr_env$items <- items invisible(self) } b_file_list <- function(self, private, service, keyring) { private$keyring_autocreate(keyring) cached <- private$get_cache(keyring) all_items <- cached$items res <- data.frame( service = vapply(all_items, `[[`, character(1L), "service_name"), username = vapply(all_items, function(x) x$user_name %||% NA_character_, character(1)), stringsAsFactors = FALSE ) if (!is.null(service)) { res[res[["service"]] == service, ] } else { res } } b_file_keyring_create <- function(self, private, keyring, password) { private$keyring_create_direct(keyring, password) } b_file_keyring_delete <- function(self, private, keyring) { self$keyring_lock(keyring) kr_file <- private$keyring_file(keyring) unlink(kr_file, recursive = TRUE, force = TRUE) invisible(self) } b_file_keyring_lock <- function(self, private, keyring) { keyring <- keyring %||% private$keyring file <- private$keyring_file(keyring) if (!file.exists(file)) { stop("The '", keyring, "' keyring does not exists, create it first!") } private$unset_keyring_pass(keyring) invisible(self) } b_file_keyring_unlock <- function(self, private, keyring, password) { file <- private$keyring_file(keyring) if (!file.exists(file)) { stop("Keyring `", keyring, "` does not exist") } private$set_keyring_pass(password, keyring) if (self$keyring_is_locked(keyring)) { private$unset_keyring_pass(keyring) b_file_error( "cannot unlock keyring", "The supplied password does not work." ) } invisible(self) } b_file_keyring_is_locked <- function(self, private, keyring) { private$keyring_autocreate(keyring) keyring <- keyring %||% private$keyring file_name <- private$keyring_file(keyring) if (!file.exists(file_name)) { stop("Keyring `", keyring, "` does not exist") } if (!file.exists(file_name) || !private$is_set_keyring_pass(keyring)) { TRUE } else { tryCatch({ cached <- private$get_cache(keyring) b_file_secret_decrypt( cached$check, cached$nonce, private$get_keyring_pass(keyring) ) FALSE }, error = function(e) { if(conditionMessage(e) == "Failed to decrypt") TRUE else stop(e) } ) } } b_file_keyring_list <- function(self, private) { kr_dir <- dirname(private$keyring_file(NULL)) files <- dir(kr_dir, pattern = "\\.keyring$", full.names = TRUE) names <- sub("\\.keyring", "", basename(files)) num_secrets <- vapply( files, function(f) length(yaml::yaml.load_file(f)$items), integer(1)) locked <- vapply( names, function(k) self$keyring_is_locked(keyring = k), logical(1)) data.frame( keyring = unname(names), num_secrets = unname(num_secrets), locked = unname(locked), stringsAsFactors = FALSE ) } b_file_keyring_default <- function(self, private) { private$keyring } b_file_keyring_set_default <- function(self, private, keyring) { private$keyring <- keyring invisible(self) } ## -------------------------------------------------------------------- ## Private b__file_keyring_create_direct <- function(self, private, keyring, password, prompt) { check_for_libsodium() keyring <- keyring %||% private$keyring prompt <- prompt %||% "Keyring password: " file_name <- private$keyring_file(keyring) if (file.exists(file_name)) { confirmation(paste("are you sure you want to overwrite", file_name, "(type `yes` if so)"), "yes") } password <- password %||% get_pass(prompt) if (is.null(password)) stop("Aborted creating keyring") ## File need to exist for $set_keyring_pass() ... dir.create(dirname(file_name), recursive = TRUE, showWarnings = FALSE) cat("", file = file_name) key <- private$set_keyring_pass(password, keyring) with_lock(file_name, private$keyring_write_file( keyring, nonce = sodium::random(24L), items = list(), key = key ) ) invisible(self) } b__file_keyring_file <- function(self, private, keyring) { keyring <- keyring %||% private$keyring keyring_dir <- getOption("keyring_file_dir", rappdirs::user_config_dir("r-keyring")) file.path(keyring_dir, paste0(keyring, ".keyring")) } b__file_keyring_read_file <- function(self, private, keyring) { check_for_libsodium() keyring <- keyring %||% private$keyring file_name <- private$keyring_file(keyring) with_lock(file_name, { stamp <- file_stamp(keyring) yml <- yaml::yaml.load_file(file_name) }) assert_that( is_list_with_names(yml, names = c("keyring_info", "items")), is_list_with_names( yml[["keyring_info"]], names = c("keyring_version", "nonce", "integrity_check") ) ) list( nonce = sodium::hex2bin(yml[["keyring_info"]][["nonce"]]), items = lapply(yml[["items"]], b__file_validate_item), check = yml[["keyring_info"]][["integrity_check"]], stamp = stamp ) } b__file_keyring_write_file <- function(self, private, keyring, nonce, items, key) { check_for_libsodium() keyring <- keyring %||% private$keyring file_name <- private$keyring_file(keyring) nonce <- nonce %||% private$get_cache(keyring)$nonce with_lock( file_name, yaml::write_yaml( list( keyring_info = list( keyring_version = as.character(getNamespaceVersion(.packageName)), nonce = sodium::bin2hex(nonce), integrity_check = b_file_secret_encrypt( paste(sample(letters, 22L, replace = TRUE), collapse = ""), nonce, key %||% private$get_keyring_pass(keyring) ) ), items = items %||% private$get_cache(keyring)$items ), file_name ) ) invisible(self) } b__file_get_keyring_pass <- function(self, private, keyring) { kr_env <- b_file_keyring_env(private$keyring_file(keyring)) if (is.null(kr_env$key)) { key <- private$set_keyring_pass(keyring = keyring) } else { key <- kr_env$key } assert_that(is.raw(key), length(key) > 0L) key } b__file_unset_keyring_pass <- function(self, private, keyring) { kr_env <- b_file_keyring_env(private$keyring_file(keyring)) kr_env$key <- NULL invisible(kr_env) } b__file_is_set_keyring_pass <- function(self, private, keyring) { !is.null(b_file_keyring_env(private$keyring_file(keyring))$key) } b__file_set_keyring_pass <- function(self, private, key, keyring) { check_for_libsodium() key <- key %||% get_pass("Keyring password: ") if (is.null(key)) stop("Aborted setting keyring password") assert_that(is_string(key)) key <- sodium::hash(charToRaw(key)) kr_env <- b_file_keyring_env(private$keyring_file(keyring)) kr_env$key <- key } b__file_update_cache <- function(self, private, keyring, nonce, check, items) { kr_env <- b_file_keyring_env(private$keyring_file(keyring)) kr <- private$keyring_read_file(keyring) nonce <- nonce %||% kr[["nonce"]] assert_that(is.raw(nonce), length(nonce) > 0L) kr_env$nonce <- nonce check <- check %||% kr[["check"]] assert_that(is.character(check), length(check) > 0L) kr_env$check <- check kr_env$items <- lapply(items %||% kr[["items"]], b__file_validate_item) kr_env$stamp <- kr$stamp kr_env } b__file_get_cache <- function(self, private, keyring) { keyring_file <- private$keyring_file(keyring) kr_env <- b_file_keyring_env(keyring_file) if (is.null(kr_env$nonce) || is.null(kr_env$stamp) || is.na(kr_env$stamp) || file_stamp(keyring_file) != kr_env$stamp) { kr_env <- private$update_cache(keyring) } assert_that(is.raw(kr_env$nonce), length(kr_env$nonce) > 0L) assert_that(is.character(kr_env$check), length(kr_env$check) > 0L) list( nonce = kr_env$nonce, items = lapply(kr_env$items, b__file_validate_item), check = kr_env$check) } ## -------------------------------------------------------------------- ## helper functions b_file_secret_encrypt <- function(secret, nonce, key) { check_for_libsodium() res <- sodium::data_encrypt( charToRaw(secret), key, nonce ) b_file_split_string(sodium::bin2hex(res)) } b_file_secret_decrypt <- function(secret, nonce, key) { check_for_libsodium() rawToChar( sodium::data_decrypt( sodium::hex2bin(b_file_merge_string(secret)), key, nonce ) ) } b_file_keyring_env <- function(file_name) { env_name <- normalizePath(file_name, mustWork = TRUE) kr_env <- b_file_keyrings[[env_name]] if (is.null(kr_env)) { kr_env <- b_file_keyrings[[env_name]] <- new.env(parent = emptyenv()) } kr_env } b_file_error <- function(problem, reason = NULL) { if (is.null(reason)) { info <- problem } else { info <- paste0(problem, ": ", reason) } stop("keyring error (file-based keyring), ", info, call. = FALSE) } b__file_validate_item <- function(item) { assert_that( is_list_with_names(item, names = c("service_name", "user_name", "secret")), is_string(item[["service_name"]]), is_string_or_null(item[["user_name"]]), is_string_or_raw(item[["secret"]]) ) invisible(item) } b_file_split_string <- function(string, width = 78L) { assert_that(is_string(string)) paste( lapply( seq.int(ceiling(nchar(string) / width)) - 1L, function(x) substr(string, x * width + 1L, x * width + width) ), collapse = "\n" ) } b_file_merge_string <- function(string) { assert_that(is_string(string)) paste(strsplit(string, "\n")[[1L]], collapse = "") } b__file_keyring_autocreate <- function(self, private, keyring) { keyring <- keyring %||% private$keyring file <- private$keyring_file(keyring) if (!file.exists(file)) { if (is_interactive()) { private$keyring_create_direct( keyring, password = NULL, prompt = paste0( "The '", keyring, "' keyring does not exist, enter a keyring password to create it: " ) ) } else { stop("The '", keyring, "' keyring does not exists, create it first!") } } } with_lock <- function(file, expr) { timeout <- getOption("keyring_file_lock_timeout", 1000) lockfile <- paste0(file, ".lck") l <- filelock::lock(lockfile, timeout = timeout) if (is.null(l)) stop("Cannot lock keyring file") on.exit(filelock::unlock(l), add = TRUE) expr } check_for_libsodium <- function() { if ("sodium" %in% loadedNamespaces()) return() tryCatch( find.package("sodium"), error = function(err) { stop( "The 'file' keyring backend needs the sodium package, ", "please install it" ) } ) tryCatch( loadNamespace("sodium"), error = function(err) { if (Sys.info()[["sysname"]] == "Linux") { stop( call. = FALSE, "Cannot load the sodium package, please make sure that its ", "system libraries are installed.\n", "On Debian and Ubuntu systems you probably need the ", "'libsodium23' package.\n", "On Fedora, CentOS, RedHat and other RPM systems you need the ", "libsodium package. \n", "Error: ", conditionMessage(err) ) } else { stop( call. = FALSE, "Cannot load the sodium package, please make sure that its ", "system libraries are installed. \n", "Error: ", conditionMessage(err) ) } } ) } keyring/R/backend-macos.R0000644000176200001440000001742214150761211014741 0ustar liggesusers #' macOS Keychain keyring backend #' #' This backend is the default on macOS. It uses the macOS native Keychain #' Service API. #' #' It supports multiple keyrings. #' #' See [backend] for the documentation of the individual methods. #' #' @family keyring backends #' @include backend-class.R #' @export #' @examples #' \dontrun{ #' ## This only works on macOS #' kb <- backend_macos$new() #' kb$keyring_create("foobar") #' kb$set_default_keyring("foobar") #' kb$set_with_value("service", password = "secret") #' kb$get("service") #' kb$delete("service") #' kb$delete_keyring("foobar") #' } backend_macos <- R6Class( "backend_macos", inherit = backend_keyrings, public = list( name = "macos", initialize = function(keyring = NULL) b_macos_init(self, private, keyring), get = function(service, username = NULL, keyring = NULL) b_macos_get(self, private, service, username, keyring), get_raw = function(service, username = NULL, keyring = NULL) b_macos_get_raw(self, private, service, username, keyring), set = function(service, username = NULL, keyring = NULL, prompt = "Password: ") b_macos_set(self, private, service, username, keyring, prompt), set_with_value = function(service, username = NULL, password = NULL, keyring = NULL) b_macos_set_with_value(self, private, service, username, password, keyring), set_with_raw_value = function(service, username = NULL, password = NULL, keyring = NULL) b_macos_set_with_raw_value(self, private, service, username, password, keyring), delete = function(service, username = NULL, keyring = NULL) b_macos_delete(self, private, service, username, keyring), list = function(service = NULL, keyring = NULL) b_macos_list(self, private, service, keyring), keyring_create = function(keyring, password = NULL) b_macos_keyring_create(self, private, keyring, password), keyring_list = function() b_macos_keyring_list(self, private), keyring_delete = function(keyring = NULL) b_macos_keyring_delete(self, private, keyring), keyring_lock = function(keyring = NULL) b_macos_keyring_lock(self, private, keyring), keyring_unlock = function(keyring = NULL, password = NULL) b_macos_keyring_unlock(self, private, keyring, password), keyring_is_locked = function(keyring = NULL) b_macos_keyring_is_locked(self, private, keyring), keyring_default = function() b_macos_keyring_default(self, private), keyring_set_default = function(keyring = NULL) b_macos_keyring_set_default(self, private, keyring), docs = function() { modifyList(super$docs(), list( . = "Store secrets in the macOS Keychain." )) } ), private = list( keyring = NULL, keyring_file = function(name) b_macos_keyring_file(self, private, name), keyring_create_direct = function(keyring, password) b_macos_keyring_create_direct(self, private, keyring, password) ) ) b_macos_init <- function(self, private, keyring) { private$keyring <- keyring invisible(self) } b_macos_get <- function(self, private, service, username, keyring) { username <- username %||% getOption("keyring_username") keyring <- private$keyring_file(keyring %||% private$keyring) res <- .Call(keyring_macos_get, utf8(keyring), utf8(service), utf8(username)) if (any(res == 0)) { stop("Key contains embedded null bytes, use get_raw()") } rawToChar(res) } b_macos_get_raw <- function(self, private, service, username, keyring) { username <- username %||% getOption("keyring_username") keyring <- private$keyring_file(keyring %||% private$keyring) .Call(keyring_macos_get, utf8(keyring), utf8(service), utf8(username)) } b_macos_set <- function(self, private, service, username, keyring, prompt) { username <- username %||% getOption("keyring_username") password <- get_pass(prompt) if (is.null(password)) stop("Aborted setting keyring key") b_macos_set_with_value(self, private, service, username, password, keyring) invisible(self) } b_macos_set_with_value <- function(self, private, service, username, password, keyring) { username <- username %||% getOption("keyring_username") keyring <- private$keyring_file(keyring %||% private$keyring) .Call(keyring_macos_set, utf8(keyring), utf8(service), utf8(username), charToRaw(password)) invisible(self) } b_macos_set_with_raw_value <- function(self, private, service, username, password, keyring) { username <- username %||% getOption("keyring_username") keyring <- private$keyring_file(keyring %||% private$keyring) .Call(keyring_macos_set, utf8(keyring), utf8(service), utf8(username), password) invisible(self) } b_macos_delete <- function(self, private, service, username, keyring) { username <- username %||% getOption("keyring_username") keyring <- private$keyring_file(keyring %||% private$keyring) .Call(keyring_macos_delete, utf8(keyring), utf8(service), utf8(username)) invisible(self) } b_macos_list <- function(self, private, service, keyring) { keyring <- private$keyring_file(keyring %||% private$keyring) res <- .Call(keyring_macos_list, utf8(keyring), utf8(service)) data.frame( service = res[[1]], username = res[[2]], stringsAsFactors = FALSE ) } b_macos_keyring_create <- function(self, private, keyring, password) { password <- password %||% get_pass() if (is.null(password)) stop("Aborted creating keyring") private$keyring_create_direct(keyring, password) invisible(self) } b_macos_keyring_list <- function(self, private) { res <- .Call(keyring_macos_list_keyring) data.frame( keyring = sub("\\.keychain(-db)?$", "", basename(res[[1]])), num_secrets = res[[2]], locked = res[[3]], stringsAsFactors = FALSE ) } b_macos_keyring_delete <- function(self, private, keyring) { self$confirm_delete_keyring(keyring) keyring <- private$keyring_file(keyring %||% private$keyring) .Call(keyring_macos_delete_keyring, utf8(keyring)) invisible(self) } b_macos_keyring_lock <- function(self, private, keyring) { keyring <- private$keyring_file(keyring %||% private$keyring) .Call(keyring_macos_lock_keyring, utf8(keyring)) invisible(self) } b_macos_keyring_unlock <- function(self, private, keyring, password) { password <- password %||% get_pass() if (is.null(password)) stop("Aborted unlocking keyring") keyring <- private$keyring_file(keyring %||% private$keyring) .Call(keyring_macos_unlock_keyring, utf8(keyring), password) invisible(self) } b_macos_keyring_is_locked <- function(self, private, keyring) { keyring <- private$keyring_file(keyring %||% private$keyring) .Call(keyring_macos_is_locked_keyring, utf8(keyring)) } b_macos_keyring_default <- function(self, private) { private$keyring } b_macos_keyring_set_default <- function(self, private, keyring) { private$keyring <- keyring invisible(self) } ## -------------------------------------------------------------------- ## Private b_macos_keyring_file <- function(self, private, name) { if (is.null(name)) { name } else if (substr(name, 1, 1) == "/" || substr(name, 1, 2) == "./") { normalizePath(name, mustWork = FALSE) } else { files <- normalizePath( paste0("~/Library/Keychains/", name, c(".keychain", ".keychain-db")), mustWork = FALSE ) if (file.exists(files[1])) { files[1] } else if (file.exists(files[2])) { files[2] } else if (darwin_version() >= "16.0.0") { files[2] } else { files[1] } } } b_macos_keyring_create_direct <- function(self, private, keyring, password) { keyring <- private$keyring_file(keyring) .Call(keyring_macos_create, utf8(keyring), password) invisible(self) } keyring/R/package.R0000644000176200001440000000402314146771774013662 0ustar liggesusers #' About the keyring package #' #' Platform independent API to many system credential store #' implementations. Currently supported: #' * Keychain on macOS, #' * Credential Store on Windows, #' * the Secret Service API on Linux, and #' * environment variables on other platforms. #' #' @section Configuring an OS-specific backend: #' #' - The default is operating system specific, and is described in #' [default_backend()]. In most cases you don't have to configure this. #' - MacOS: [backend_macos] #' - Linux: [backend_secret_service] #' - Windows: [backend_wincred] #' - Or store the secrets in environment variables on other operating #' systems: [backend_env] #' #' @section Query secret keys in a keyring: #' #' Each keyring can contain one or many secrets (keys). A key is defined by #' a service name and a password. Once a key is defined, it persists in the #' keyring store of the operating system. This means the keys persist beyond #' the termination of and R session. Specifically, you can define a key #' once, and then read the key value in completely independent R sessions. #' #' - Setting a secret interactively: [key_set()] #' - Setting a secret from a script, i.e. non-interactively: #' [key_set_with_value()] #' - Reading a secret: [key_get()] #' - Listing secrets: [key_list()] #' - Deleting a secret: [key_delete()] #' #' @section Managing keyrings: #' #' A keyring is a collection of keys that can be treated as a unit. #' A keyring typically has a name and a password to unlock it. #' #' - [keyring_create()] #' - [keyring_delete()] #' - [keyring_list()] #' - [keyring_lock()] #' - [keyring_unlock()] #' #' Note that all platforms have a default keyring, and `key_get()`, etc. #' will use that automatically. The default keyring is also convenient, #' because the OS unlocks it automatically when you log in, so secrets #' are available immediately. #' #' You only need to explicitly deal with keyrings and the `keyring_*` #' functions if you want to use a different keyring. #' #' @useDynLib keyring, .registration = TRUE "_PACKAGE" keyring/R/api.R0000644000176200001440000002260414150760137013027 0ustar liggesusers #' Operations on keys #' #' These functions manipulate keys in a keyring. You can think of a keyring #' as a secure key-value store. #' #' `key_get` queries a key from the keyring. #' #' `key_get_raw` queries a key and returns it as a raw vector. #' Most credential stores allow storing a byte sequence with embedded null #' bytes, and these cannot be represented as traditional null bytes #' terminated strings. If you don't know whether the key contains an #' embedded null, it is best to query it with `key_get_raw` instead of #' `key_get`. #' #' `key_set` sets a key in the keyring. The contents of the key is read #' interactively from the terminal. #' #' `key_set_with_value` is the non-interactive pair of `key_set`, to set #' a key in the keyring. #' #' `key_set_raw_with_value` sets a key to a byte sequence from a raw #' vector. #' #' `key_delete` deletes a key. #' #' `key_list` lists all keys of a keyring, or the keys for a certain #' service (if `service` is not `NULL`). #' #' ## Encodings #' #' On Windows, if required, an encoding can be specified using either #' an R option (`keyring.encoding_windows`) or environment variable #' (`KEYRING_ENCODING_WINDOWS`). This will be applied when both #' getting and setting keys. The option takes precedence over the #' environment variable, if both are set. #' #' This is reserved primarily for compatibility with keys set with #' other software, such as Python's implementation of keyring. For a #' list of encodings, use [iconvlist()], although it should be noted #' that not _every_ encoding can be properly converted, even for #' trivial cases. For best results, use UTF-8 if you can. #' #' @param service Service name, a character scalar. #' @param username Username, a character scalar, or `NULL` if the key #' is not associated with a username. #' @param password The secret to store. For `key_set`, it is read from #' the console, interactively. `key_set_with_value` can be also used #' in non-interactive mode. #' @param keyring For systems that support multiple keyrings, specify #' the name of the keyring to use here. If `NULL`, then the default #' keyring is used. See also [has_keyring_support()]. #' @param prompt The character string displayed when requesting the secret #' #' @return `key_get` returns a character scalar, the password or other #' confidential information that was stored in the key. #' #' `key_list` returns a list of keys, i.e. service names and usernames, #' in a data frame. #' #' @export #' @examples #' # These examples use the default keyring, and they are interactive, #' # so, we don't run them by default #' \dontrun{ #' key_set("R-keyring-test-service", "donaldduck") #' key_get("R-keyring-test-service", "donaldduck") #' if (has_keyring_support()) key_list(service = "R-keyring-test-service") #' key_delete("R-keyring-test-service", "donaldduck") #' #' ## This is non-interactive, assuming that that default keyring #' ## is unlocked #' key_set_with_value("R-keyring-test-service", "donaldduck", #' password = "secret") #' key_get("R-keyring-test-service", "donaldduck") #' if (has_keyring_support()) key_list(service = "R-keyring-test-service") #' key_delete("R-keyring-test-service", "donaldduck") #' #' ## This is interactive using backend_file #' ## Set variables to be used in keyring #' kr_name <- "my_keyring" #' kr_service <- "my_database" #' kr_username <- "my_username" #' #' ## Create a keyring and add an entry using the variables above #' kb <- keyring::backend_file$new() #' ## Prompt for the keyring password, used to unlock keyring #' kb$keyring_create(kr_name) #' ## Prompt for the secret/password to be stored in the keyring #' kb$set(kr_service, username=kr_username, keyring=kr_name) #' # Lock the keyring #' kb$keyring_lock(kr_name) #' #' ## The keyring file is stored at ~/.config/r-keyring/ on Linux #' #' ## Output the stored password #' keyring::backend_file$new()$get(service = kr_service, #' user = kr_username, #' keyring = kr_name) #' } key_get <- function(service, username = NULL, keyring = NULL) { assert_that(is_non_empty_string(service)) assert_that(is_string_or_null(username)) default_backend()$get(service, username, keyring = keyring) } #' @export #' @rdname key_get key_get_raw <- function(service, username = NULL, keyring = NULL) { assert_that(is_non_empty_string(service)) assert_that(is_string_or_null(username)) default_backend()$get_raw(service, username, keyring = keyring) } #' @export #' @rdname key_get key_set <- function(service, username = NULL, keyring = NULL, prompt = "Password: ") { assert_that(is_non_empty_string(service)) assert_that(is_string_or_null(username)) default_backend()$set(service, username, keyring = keyring, prompt = prompt) } #' @export #' @rdname key_get key_set_with_value <- function(service, username = NULL, password = NULL, keyring = NULL) { assert_that(is_non_empty_string(service)) assert_that(is_string(password)) default_backend()$set_with_value(service, username, password, keyring = keyring) } #' @export #' @rdname key_get key_set_with_raw_value <- function(service, username = NULL, password = NULL, keyring = NULL) { assert_that(is_non_empty_string(service)) assert_that(is.raw(password)) default_backend()$set_with_raw_value(service, username, password, keyring = keyring) } #' @export #' @rdname key_get key_delete <- function(service, username = NULL, keyring = NULL) { assert_that(is_non_empty_string(service)) assert_that(is_string_or_null(username)) default_backend()$delete(service, username, keyring = keyring) } #' @export #' @rdname key_get key_list <- function(service = NULL, keyring = NULL) { assert_that(is_non_empty_string_or_null(service)) default_backend()$list(service, keyring = keyring) } #' Operations on keyrings #' #' On most platforms `keyring` supports multiple keyrings. This includes #' Windows, macOS and Linux (Secret Service) as well. A keyring is a #' collection of keys that can be treated as a unit. A keyring typically #' has a name and a password to unlock it. Once a keyring is unlocked, #' it remains unlocked until the end of the user session, or until it is #' explicitly locked again. #' #' Platforms typically have a default keyring, which is unlocked #' automatically when the user logs in. This keyring does not need to be #' unlocked explicitly. #' #' You can configure the keyring to use via R options or environment #' variables (see [default_backend()]), or you can also specify it #' directly in the [default_backend()] call, or in the individual #' `keyring` calls. #' #' `has_keyring_support` checks if a backend supports multiple keyrings. #' #' `keyring_create` creates a new keyring. It asks for a password if no #' password is specified. #' #' `keyring_list` lists all existing keyrings. #' #' `keyring_delete` deletes a keyring. Deleting a non-empty keyring #' requires confirmation, and the default keyring can only be deleted if #' specified explicitly. On some backends (e.g. Windows Credential Store), #' the default keyring cannot be deleted at all. #' #' `keyring_lock` locks a keyring. On some backends (e.g. Windows #' Credential Store), the default keyring cannot be locked. #' #' `keyring_unlock` unlocks a keyring. If a password is not specified, #' it will be read in interactively. #' #' `keyring_is_locked` queries whether a keyring is locked. #' #' @param keyring The name of the keyring to create or to operate on. #' For functions other than `keyring_create`, it can also be `NULL` to #' select the default keyring. #' @param password The initial password or the password to unlock the #' keyring. If not specified or `NULL`, it will be read from the console. #' #' @export #' @examples #' default_backend() #' has_keyring_support() #' backend_env$new()$has_keyring_support() #' #' ## This might ask for a password, so we do not run it by default #' ## It only works if the default backend supports multiple keyrings #' \dontrun{ #' keyring_create("foobar") #' key_set_with_value("R-test-service", "donaldduck", password = "secret", #' keyring = "foobar") #' key_get("R-test-service", "donaldduck", keyring = "foobar") #' key_list(keyring = "foobar") #' keyring_delete(keyring = "foobar") #' } has_keyring_support <- function() { default_backend()$has_keyring_support() } #' @export #' @rdname has_keyring_support keyring_create <- function(keyring, password = NULL) { assert_that( is_string(keyring), is_string_or_null(password) ) default_backend()$keyring_create(keyring, password) } #' @export #' @rdname has_keyring_support keyring_list <- function() { default_backend()$keyring_list() } #' @export #' @rdname has_keyring_support keyring_delete <- function(keyring = NULL) { assert_that(is_string_or_null(keyring)) default_backend()$keyring_delete(keyring) } #' @export #' @rdname has_keyring_support keyring_lock <- function(keyring = NULL) { assert_that(is_string_or_null(keyring)) default_backend()$keyring_lock(keyring) } #' @export #' @rdname has_keyring_support keyring_unlock <- function(keyring = NULL, password = NULL) { assert_that(is_string_or_null(keyring)) default_backend()$keyring_unlock(keyring, password) } #' @export #' @rdname has_keyring_support keyring_is_locked <- function(keyring = NULL) { assert_that(is_string_or_null(keyring)) default_backend()$keyring_is_locked(keyring) } keyring/NEWS.md0000644000176200001440000000321414150764320013022 0ustar liggesusers # keyring 1.3.0 * `keyring_create()` and also all backends that support multiple keyrings now allow passing the password when creating a new keyring (#114). * `key_set()` can now use a custom prompt (@pnacht, #112). * keyring now handled better the 'Cancel' button when requesting a password in RStudio, and an error is thrown in this case (#106). # keyring 1.2.0 * It is now possible to specify the encoding of secrets on Windows (#88, @awong234). * The `get_raw()` method of the Secret Service backend works now (#87). * Now the file backend is selected by default on Unix systems if Secret Service is not available or does not work (#95, @nwstephens). * The file backend now works with keys that do not have a username. * All backends use the value of the `keyring_username` option, if set, as the default username (#60). # keyring 1.1.0 * File based backend (#53, @nbenn). * Fix bugs in `key_set()` on Linux (#43, #51). * Windows: support non-ascii characters and spaces in `key_list()` `service` and `keyring` (#48, #49, @javierluraschi). * Add support for listing service keys for env backend (#58, @javierluraschi). * keyring is now compatible with R 3.1.x and R 3.2.x. * libsecret is now optional on Linux. If not available, keyring is built without the Secret Service backend (#55). * Fix the `get_raw()` method on Windows. * Windows: `get()` tries the UTF-16LE encoding if the sting has embedded zero bytes. This allows getting secrets that were set in Credential Manager (#56). * Windows: fix `list()` when some secrets have no `:` at all (these were probably set externally) (#44). # keyring 1.0.0 First public release. keyring/MD50000644000176200001440000000537114151113235012234 0ustar liggesusers6c37a766fd4ea30c580cf94d53d9bc11 *DESCRIPTION 861402e081b774b5ea5f27d861c6ba94 *LICENSE 00556c6e3c95c8dcb180c6b8e6fb6933 *NAMESPACE ef0c3533957f2d6a79246e760f1412b9 *NEWS.md c0cf6c05e436a4ec02fb40f47615a41d *R/api.R b5b7709c210f707ace72fa0381c4bf46 *R/assertions.R c542cba2540a1736cdcf97d31e2ffde1 *R/backend-class.R 161a1db211ac738f93af0dc25aad8727 *R/backend-env.R a7ba2bd062c9034a14766efa4f53f4f4 *R/backend-file.R b50ef5a13841042b960577f2b255bec5 *R/backend-macos.R 447414151b8f8e2fd78fc1af51e1bb13 *R/backend-secret-service.R 5a32875d6c54603f0e0cca812ddb821c *R/backend-wincred.R dcdf9ad591fd25f08c2e80897a5ab14a *R/default_backend.R 38baf7dae344a876416a6b077ee33ad4 *R/package.R 3b218c5cb6c9fbf2a13ccd30f182c57a *R/pass.R 29f3016c1e412fd3862086fa5892d155 *R/utils.R 1a0092a506131f85403fe27079b84106 *README.md 548930783cfb34e0ebe65c1b3dbdc706 *cleanup e0b781edc0824c2440ce9038d125f5c5 *configure 68b329da9893e34099c7d8ad5cb9c940 *configure.win d7547609ac71a99ddc3e9b8c98e3652e *inst/development-notes.md 0329e987cce90ef2037153bd138a38ea *man/b_wincred_decode.Rd ecb560392516129010e9dee9d5006736 *man/b_wincred_decode_auto.Rd e684dd7302b958fa3408482c2957ea8c *man/b_wincred_get.Rd f1e5714dd9532c8e86ebe9076dcbb451 *man/b_wincred_set_with_raw_value.Rd ac2c0e085f63f1b0761f97ce7d0f541e *man/backend.Rd bf1451d1e551d3f771ad7efeb319c50c *man/backend_env.Rd 2932f4a9eeb8adebda591daab5af5ab0 *man/backend_file.Rd 4652d8d9f29152fe68df7fa5c9a3638a *man/backend_keyrings.Rd 08751ee749884db7a9a83bcdf4949543 *man/backend_macos.Rd 8fff920d5b8bb2f9ed2d6147b8501d76 *man/backend_secret_service.Rd 171785ca293eefae5b39d07bc93aa572 *man/backend_wincred.Rd 3b263a211f1ddb6301d7a5faafbf0210 *man/backends.Rd 11195c7d76b057d18a66c155a15055f2 *man/has_keyring_support.Rd 7b8a17cf7f895709d44be36df2c1c3fb *man/key_get.Rd f96a36031ae833605f9d25c561712f21 *man/keyring-package.Rd 68b329da9893e34099c7d8ad5cb9c940 *src/Makevars.in d41d8cd98f00b204e9800998ecf8427e *src/Makevars.win 8f69c386edc4c3e9bc2ece05bffb106b *src/init.c a216c3f684686675b748cf4f167832d6 *src/keyring_macos.c 636f093d8c9945a863ecb819b99782e8 *src/keyring_secret_service.c 2ed94040670837c68870ea32e806527a *src/keyring_wincred.c c271a01509f3b47c638e48fe545511e2 *tests/testthat.R ae825f5129036efff1a1298ffdbd2a84 *tests/testthat/helper.R a9dffdff6cae5abcecbccad7565768e3 *tests/testthat/test-common.R 693c8fc34342d9afddb433ff076b80ac *tests/testthat/test-default-backend.R d2145ab0fb7b5d21bc08afec6544b2f8 *tests/testthat/test-encoding.R 3a4aec9bada2bf05f2d5e35e3403c5ab *tests/testthat/test-env.R 5a729966d76420ace565b62d36bfca2c *tests/testthat/test-file.R e3a314e13be8d04650e1656c78976231 *tests/testthat/test-macos.R 5e1b99abbebaa70dd0edee03b99cb0e9 *tests/testthat/test-secret-service.R 4c2c4be07093b40b727b13e000f8275c *tests/testthat/test-wincred.R keyring/inst/0000755000176200001440000000000014146771774012721 5ustar liggesuserskeyring/inst/development-notes.md0000644000176200001440000002256614146771774016726 0ustar liggesusers # Notes for `keyring` developers * [Introduction](#introduction) * [Backend implementation details](#backend-implementation-details) * [macOS Keychain](#macos-keychain) * [Windows Credential Store](#windows-credential-store) * [Multiple keyrings](#multiple-keyrings) * [Secret Service API](#secret-service-api) * [Unloading the package](#unloading-the-package) * [Environment variable backend](#environment-variable-backend) * [Limits on secret sizes](#limits-on-secret-sizes) * [macOS Keychain](#macos-keychain-1) * [Windows Credential Store](#windows-credential-store-1) * [Linux Secret Service](#linux-secret-service) * [Testing the package](#testing-the-package) * [Test keyrings and keyring items](#test-keyrings-and-keyring-items) ## Introduction This document is aimed at developers that want to improve the existing keyring backends, or want to implement new backends. You don't need it for using `keyring`. ## Backend implementation details ### macOS Keychain On macOS, the keyrings are store in files. We handle the user's keyrings, these are in `~/Library/Keychains`. They are files with extension `.keychain` (before Sierra) or `.keychain-db` (starting from Sierra). Whenever a keyring is specified, it can be a symbolic name (e.g. `login`), or an absolute filename (e.g. `/Users/gaborcsardi/Library/Keychains/login.keychain`). If a symbolic name is specified, we look for both `.keychain` and `.keychain-db` files: * If the `.keychain` file exists, we use that. * Otherwise, if the `.keychain-db` file exists, we use that. * Otherwise, if the system is Sierra or later, we use `.keychain-db`. * Otherwise we use the `.keychain` file. ### Windows Credential Store We use the *old* API to the Windows Credential Store. See e.g. https://msdn.microsoft.com/en-us/library/windows/desktop/aa374804%28v%3Dvs.85%29.aspx and also the `CREDENTIAL`, `CredWrite`, `CredDelete`, `CredEnumerate` there. The reason for this is that MinGW does not provide a wrapper to the *new* API, introduced in Windows 8.x. This also means that we don't have access to the secrets stored by recent Windows web browsers (IE and Edge). #### Multiple keyrings The *old* Windows Credential Store does not support multiple keyrings, and it does not support locking and unlocking the (single) keyring, either. For every (non-default) keyring, we create a credential, with target name `keyring::`. This credential contains metadata about the keyring. It currently has the following (DCF, Debian Control File) format: ``` Version: 1.0.0 Verify: NgF+vkkNsOoSnXVXt249u6xknskhDasMIhE8Uuzpl/w= Salt: some random salt ``` The `Verify` tag is used to check if the keyring password that was specified to unlock the keyring, was correct. The `Salt` tag is used to salt the SHA256 hash, to make it more secure. It is generated randomly when the keyring is created. When a keyring is unlocked, the user specifies the pass phrase of the keyring. We create the SHA256 hash of this pass phrase, and this will be the AES key to encrypt/decrypt the items in the keyring. When unlocking a keyring, we use the `Verify` field, to see if the supplied password indeed hashes to the correct AES key. If it can decrypt the `Verify` string, then it is correct. We also store the AES key in the keyring, in a session credential with target name `keyring::unlocked`. A session credential's life time is the life time of a single login session. The AES key is stored in a base64 encoded form, e.g.: ``` JvL7srqc0X1vVnqbSayFnIkJZoe2xMOWoDh+aBR9DJc= ``` The credentials of the keyring itself have target names as `keyring:service:username`, where the username may be empty. If keyring is empty, then the credential is considered to be on the default keyring, and it is not encrypted. Credentials on other keyrings are encrypted using the AES key of the keyring. The random initialization vector of the encryption is stored as the first 16 bytes of the keyring item. When we `set` a key, we need to: 1. Check if the key is on the default keyring. 2. If 1. is TRUE, then just set the key, using target name `:service:username` (username might be empty, servicename not), and finish. 3. Check if the keyring exists. 4. If 3. is FALSE, then error and finish. 5. Check that the keyring is unlocked. 6. If 5. is FALSE, then prompt the user and unlock the keyring. 7. Encrypt the key with the AES key, and store the encrypted key using target name `keyring:service:username` (again, username might be empty, service name not). When we `get` a key, we need to: 1. Check if the key is on the default keyring. 2. If 1. is TRUE, then we just get the key, using target name `:service:username`. 3. Check if the keyring is locked. 4. If 3. is TRUE, then prompt the user and unlock the keyring. 5. Get the AES key from the unlocked keyring. 6. Get the key and use the AES key to decrypt it. To unlock a keyring we need to: 1. Get the keyring password and SHA256 hash it. 2. Try to decrypt the `Verify` field of the keyring metadata, to see if the password was correct. 3. If the password was not correct, then error out. 4. If the password was correct, then store the AES key under target name `keyring::unlocked`. The C functions for this backend do not know about multiple keyrings at all, we just use them to get/set/delete/list "regular" credentials in the credential store. ### Secret Service API The Secret Service API works on DBUS, and multiple daemons support it. On GNOME based systems, typically gnome-keyring, and on KDE based systems typically KWallet is used. Linux systems lacking a GUI typically do not run a secret service daemon, and `keyring` cannot use the OS credential store on these systems. The default backend is the environment variable based one (`backend_env`) on these systems. #### Prompts For the Secret Service backend, it is currently not possible to specify a password for some functions. Instead, the password is read in interactively on this backend, by the system. This currently happens when a new keyring is created, and when a keyring is unlocked. #### Unloading the package `libsecret` uses `libglib`, and `libglib` does not allow unloading the package. More precisely, if you unload the `keyring` package on Linux, then some `libglib` threads will stay around. If you then reload the package, R will crash. This is a known limitation of `libglib`, and there is currently no way around it. For `keyring` a workaround would be to use `libdbus` to communicate with the Secret Service daemon directly. `libdbus` is a standalone DBUS library, it does not use `libglib`. There is an issue for this in our issue tracker, but no plans currently about when it would happen: https://github.com/r-lib/keyring/issues/15 This limitation is especially annoying for development with `devtools`, because `devtools::load_all()`, `devtools::test()`, etc. try to unload and reload the package, which results in crashes. A workaround is to run the tests from the command line: ``` R -e 'devtools::test()' ``` ### Environment variable backend When an item has a username on this backend, the service name and the username is separated with a colon (`:`) character in the created environment variable: ``` service:username ``` Note that shells typically cannot read or write these environment variables. Programming languages, i.e. `Sys.getenv()` and `Sys.setenv()` in R, or the similar functions in Python, C, etc. can read and write them just fine. ## Limits on secret sizes Various backends (and platforms) have various limits on the size of the secrets they can store. In general, the safest to assume that the secret can be maximum about 400 bytes long. If you want to store something longer, encrypt it with AES, and store it in a regular file, and store the AES key in the keyring. See the `openssl` package for AES encryption and decryption. ### macOS Keychain The macOS keychain can store very big secrets. In our experiments it had no problems with a secret of 1GB, although that is already a little slow to encrypt and decrypt. The real limit is probably (much) higher, and it could be the limit on the keychain file's size. ### Windows Credential Store According to the documentation in the Windows Credential Store the maximum secret size is 512 bytes: https://msdn.microsoft.com/en-us/library/windows/desktop/aa374788%28v%3Dvs.85%29.aspx In practice, on Windows 8.1 we managed to store secrets of up to 2560 bytes. Other Windows versions might have other limits, though. Note that if you use the non-default keyring, then the actual secret stored by `keyring` is longer, because it is encrypted manually, and also BASE64 encoded. Hence the advised 400 bytes limit. ### Linux Secret Service According to our tests with the Secret Service, it can store secrets of at least 25MB. We used an Ubuntu 16.04 system and the gnome-keyring daemon for this. Note, however, that these large secrets only work if you first create a small keyring item, and then update its contents. Creating a large keyring item straight away gives an error. ## Testing the package Testing the package on CIs or local machines is tricky. Here are some notes. ### Test keyrings and keyring items When running tests (and examples), the package only creates keyrings with a name prefix `Rkeyringtest`, and on other keyrings it only creates services with the name prefix `R-keyring-test-service-`. This makes it easier to remove the leftover keyrings and keyring items manually from the system keyring. keyring/cleanup0000755000176200001440000000004614150770260013301 0ustar liggesusers#!/usr/bin/env sh rm -f src/Makevars keyring/configure0000755000176200001440000000200414150770260013627 0ustar liggesusers#!/usr/bin/env sh UNAME=`uname` if [ "$UNAME" = "Darwin" ]; then echo "PKG_LIBS=-framework Security" > src/Makevars else if which pkg-config >/dev/null 2>/dev/null && pkg-config libsecret-1; then echo "PKG_CFLAGS=-DHAS_LIBSECRET $(pkg-config --cflags libsecret-1)" > src/Makevars echo "PKG_LIBS=$(pkg-config --libs libsecret-1)" >> src/Makevars else if [ -n "$LIBSECRET_CFLAGS" && -n "$LIBSECRET_LIBS" ]; then echo "PKG_CFLAGS=-DHAS_LIBSECRET $LIBSECRET_CFLAGS" > src/Makevars echo "PKG_LIBS=$LIBSECRET_LIBS" >> src/Makevars else echo "Could not find libsecret headers or libs." echo "On Ubuntu, you need to install libsecret-1-dev via apt." echo "On RedHat, Fedora, and CentOS, you need to install libsecret-devel via yum or dnf." echo "Note that in addition to libsecret, you either need pkg-config or set the" echo "LIBSECRET_CFLAGS and LIBSECRET_LIBS environment variables." echo echo "This keyring build will not support the libsecret backend." fi fi fi