keyring/0000755000176200001440000000000014535455142011732 5ustar liggesuserskeyring/NAMESPACE0000644000176200001440000000141714144472216013151 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/LICENSE0000644000176200001440000000005514521172423012730 0ustar liggesusersYEAR: 2023 COPYRIGHT HOLDER: keyring authors keyring/README.md0000644000176200001440000000726614521172457013224 0ustar liggesusers # keyring [![R-CMD-check](https://github.com/r-lib/keyring/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/keyring/actions/workflows/R-CMD-check.yaml) [![](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) [![Codecov test coverage](https://codecov.io/gh/r-lib/keyring/branch/main/graph/badge.svg)](https://app.codecov.io/gh/r-lib/keyring?branch=main) keyring provides a way to securely manage secrets using your operating system’s credential store. Once a secret is defined, it persists in a “keyring” across multiple R sessions. keyring is an alternative to using environment variables that’s a bit more secure because your secret is never stored in plain text, meaning that you can for instance never accidentally upload it to GitHub. For more security, you can also store secrets in a custom keyring that always requires a password to unlock. keyring currently supports: - The macOS Keychain (`backend_macos`). - The Windows Credential Store (`backend_wincred`). - The Linux Secret Service API (`backend_secret_service`). It also provides two backends that are available on all platforms: - Encrypted files (`backend_file`) - Environment variables (`backend_env`). ## Installation Install the package from CRAN: ``` r # install.packages("pak") pak::pak("keyring") ``` We recommend using pak to install keyring as it will ensure that Linux system requirements are automatically installed (for instance Ubuntu requires `libsecret-1-dev`, `libssl-dev`, and `libsodium-dev`). To install the development version from GitHub, use: ``` r pak::pak("r-lib/keyring") ``` ## Usage The simplest usage only requires `key_set()` and `key_get()`: ``` r # Interactively save a secret. This avoids typing the value of the secret # into the console as this could be recorded in your `.Rhistory` key_set("secret-name") # Later retrieve that secret key_get("secret-name") ``` Each secret is associated with a keyring. By default, keyring will use the OS keyring (see `default_backend()` for details), which is automatically unlocked when you log into your computer account. That means while the secret is stored securely, it can be accessed by other processes. If you want greater security you can create a custom keyring that you manually lock and unlock. That will require you to enter a custom password every time you want to access your secret. ``` r keyring_create("mypackage") key_set("secret-name", keyring = "mypackage") key_get("secret-name", keyring = "mypackage") ``` Accessing the key unlocks the keyring, so if you’re being really careful, you might want to lock it after you’ve retrieved the value with `keyring_lock()`. ### GitHub When you use keyring on GitHub, it will fall back to the environment variable backend. That means if you want to use `key_get("mysecret")` you need to do two things: - Add a [new action secret](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository) to your repository. - Make the secret available in your workflow `.yml`, for instance ``` yaml env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} R_KEEP_PKG_SOURCE: yes MY_SECRET: ${{ secrets.my_secret }} ``` The envvar backend doesn’t support custom keyrings, so if you’re using one locally you’ll need to use the default keyring on GitHub. ## 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. keyring/man/0000755000176200001440000000000014521172457012505 5ustar liggesuserskeyring/man/backend_wincred.Rd0000644000176200001440000000204514144472216016074 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.Rd0000644000176200001440000000150114144472216015537 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.Rd0000644000176200001440000000225314144472216020702 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.Rd0000644000176200001440000000243114144472216015230 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.Rd0000644000176200001440000000145414144472216016234 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.Rd0000644000176200001440000000115214144472216015356 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.Rd0000644000176200001440000000367314521172457016307 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: \if{html}{\out{
}}\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) }\if{html}{\out{
}} \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.Rd0000644000176200001440000000452214521172457014366 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: \if{html}{\out{
}}\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) }\if{html}{\out{
}} 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.Rd0000644000176200001440000000435214144472216014547 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.Rd0000644000176200001440000000162014535445650015571 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.} } \description{ Get a key from a Wincred keyring } \keyword{internal} keyring/man/keyring-package.Rd0000644000176200001440000000664414535446175016055 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/keyring-package.R, R/package.R \docType{package} \name{keyring-package} \alias{keyring} \alias{keyring-package} \title{keyring: Access the System Credential Store from R} \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 simple, platform independent stores implemented with environment variables or encrypted files. Additional storage back-ends can be added easily. 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://keyring.r-lib.org/} \item \url{https://github.com/r-lib/keyring} \item Report bugs at \url{https://github.com/r-lib/keyring/issues} } Useful links: \itemize{ \item \url{https://keyring.r-lib.org/} \item \url{https://github.com/r-lib/keyring} \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 Posit Software, PBC [copyright holder, funder] } } \keyword{internal} keyring/man/has_keyring_support.Rd0000644000176200001440000000565114151122155017067 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.Rd0000644000176200001440000000266414521172457017460 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: \if{html}{\out{
}}\preformatted{is_available = function(report_error = FALSE) }\if{html}{\out{
}} 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.Rd0000644000176200001440000000071714144472216017265 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.Rd0000644000176200001440000001104514150723132014412 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/DESCRIPTION0000644000176200001440000000327214535455142013444 0ustar liggesusersPackage: keyring Title: Access the System Credential Store from R Version: 1.3.2 Authors@R: c( person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", role = c("aut", "cre")), person("Alec", "Wong", role = "ctb"), person("Posit Software, PBC", role = c("cph", "fnd")) ) 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 simple, platform independent stores implemented with environment variables or encrypted files. Additional storage back-ends can be added easily. License: MIT + file LICENSE URL: https://keyring.r-lib.org/, https://github.com/r-lib/keyring BugReports: https://github.com/r-lib/keyring/issues Depends: R (>= 3.6) Imports: askpass, assertthat, filelock, openssl, R6, rappdirs, sodium, tools, utils, yaml Suggests: callr, covr, mockery, testthat (>= 3.0.0), withr Config/Needs/website: tidyverse/tidytemplate Config/testthat/edition: 3 Encoding: UTF-8 RoxygenNote: 7.2.3 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' 'keyring-package.R' 'package.R' 'pass.R' 'utils.R' NeedsCompilation: yes Packaged: 2023-12-10 23:58:00 UTC; gaborcsardi Author: Gábor Csárdi [aut, cre], Alec Wong [ctb], Posit Software, PBC [cph, fnd] Maintainer: Gábor Csárdi Repository: CRAN Date/Publication: 2023-12-11 00:40:02 UTC keyring/tests/0000755000176200001440000000000014521172457013074 5ustar liggesuserskeyring/tests/testthat/0000755000176200001440000000000014535455142014734 5ustar liggesuserskeyring/tests/testthat/test-wincred.R0000644000176200001440000000573714521172457017503 0ustar liggesuserstest_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.R0000644000176200001440000000175414521172457016633 0ustar liggesuserstest_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.R0000644000176200001440000000413014521172457021043 0ustar liggesusersopts <- 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.R0000644000176200001440000000372014521172457020761 0ustar liggesusersopts <- 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.R0000644000176200001440000000772414521172457017634 0ustar liggesuserstest_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.R0000644000176200001440000002034514521172457016757 0ustar liggesuserstest_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_s3_class(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_s3_class(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_s3_class(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.R0000644000176200001440000000246414521152473016340 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.R0000644000176200001440000000753414521172457017147 0ustar liggesuserstest_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.R0000644000176200001440000000372514521172457017333 0ustar liggesuserswithr::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.R0000644000176200001440000000061214521172457015056 0ustar liggesusers# This file is part of the standard setup for testthat. # It is recommended that you do not modify it. # # Where should you do additional test configuration? # Learn more about the roles of various files in: # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview # * https://testthat.r-lib.org/articles/special-files.html library(testthat) library(keyring) test_check("keyring") keyring/src/0000755000176200001440000000000014535450207012516 5ustar liggesuserskeyring/src/init.c0000644000176200001440000000747414322212134013626 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(void); 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(void); 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.c0000644000176200001440000004044414535445207017431 0ustar liggesusers /* Avoid warning about empty compilation unit. */ void keyring_secret_service_dummy(void) { } #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(void) { 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(void) { 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(void) { 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("%s", 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("%s", 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(void) { 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(void) { 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.c0000644000176200001440000004427714326440535015533 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; # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" status = SecKeychainGetStatus(keychain, &keychainStatus); # pragma GCC diagnostic pop 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))); # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" OSStatus status = SecKeychainFindGenericPassword( keychain, (UInt32) strlen(cservice), cservice, (UInt32) strlen(cusername), cusername, &length, &data, /* itemRef = */ NULL); # pragma GCC diagnostic pop if (keychain != NULL) CFRelease(keychain); keyring_macos_handle_status("cannot get password", status); result = PROTECT(allocVector(RAWSXP, length)); memcpy(RAW(result), data, length); # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" SecKeychainItemFreeContent(NULL, data); # pragma GCC diagnostic pop 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 */ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" OSStatus status = SecKeychainFindGenericPassword( keychain, (UInt32) strlen(cservice), cservice, (UInt32) strlen(cusername), cusername, /* passwordLength = */ NULL, /* passwordData = */ NULL, &item); # pragma GCC diagnostic pop if (status == errSecItemNotFound) { # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" status = SecKeychainAddGenericPassword( keychain, (UInt32) strlen(cservice), cservice, (UInt32) strlen(cusername), cusername, (UInt32) LENGTH(password), RAW(password), /* itemRef = */ NULL); # pragma GCC diagnostic pop } else { # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" status = SecKeychainItemModifyAttributesAndData( item, /* attrList= */ NULL, (UInt32) LENGTH(password), RAW(password)); # pragma GCC diagnostic pop 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; # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" OSStatus status = SecKeychainFindGenericPassword( keychain, (UInt32) strlen(cservice), cservice, (UInt32) strlen(cusername), cusername, /* *passwordLength = */ NULL, /* *passwordData = */ NULL, &item); # pragma GCC diagnostic pop if (status != errSecSuccess) { if (keychain != NULL) CFRelease(keychain); keyring_macos_error("cannot delete password", status); } # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" status = SecKeychainItemDelete(item); # pragma GCC diagnostic pop 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... */ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" if (SecKeychainItemGetTypeID() != CFGetTypeID(item)) { SET_STRING_ELT(VECTOR_ELT(result, 0), idx, mkChar("")); SET_STRING_ELT(VECTOR_ELT(result, 1), idx, mkChar("")); return; } # pragma GCC diagnostic pop # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" OSStatus status = SecKeychainItemCopyContent(item, &class, &attrList, /* length = */ NULL, /* outData = */ NULL); # pragma GCC diagnostic pop 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)); # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" SecKeychainItemFreeContent(&attrList, NULL); # pragma GCC diagnostic pop } 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; # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" status = SecKeychainCopyDomainSearchList( kSecPreferencesDomainUser, &keyrings); # pragma GCC diagnostic pop 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); # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" status = SecKeychainSetDomainSearchList( kSecPreferencesDomainUser, newkeyrings); # pragma GCC diagnostic pop 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(void) { CFArrayRef keyrings = NULL; # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" OSStatus status = SecKeychainCopyDomainSearchList(kSecPreferencesDomainUser, &keyrings); # pragma GCC diagnostic pop 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; # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" status = SecKeychainGetStatus(keychain, &kstatus); # pragma GCC diagnostic pop 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; # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" OSStatus status = SecKeychainCopyDomainSearchList( kSecPreferencesDomainUser, &keyrings); # pragma GCC diagnostic pop 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); # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" status = SecKeychainSetDomainSearchList( kSecPreferencesDomainUser, newkeyrings); # pragma GCC diagnostic pop 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; # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" OSStatus status = SecKeychainGetStatus(keychain, &kstatus); # pragma GCC diagnostic pop 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(void) { 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.win0000644000176200001440000000000014144472216014774 0ustar liggesuserskeyring/src/Makevars.in0000644000176200001440000000000114326440532014604 0ustar liggesusers keyring/src/keyring_wincred.c0000644000176200001440000001031514322212134016032 0ustar liggesusers /* Avoid warning about empty compilation unit. */ void keyring_wincred_dummy(void) { } #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.win0000755000176200001440000000000114144472216014421 0ustar liggesusers keyring/R/0000755000176200001440000000000014521172457012133 5ustar liggesuserskeyring/R/backend-secret-service.R0000644000176200001440000001676314521152473016577 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.R0000644000176200001440000000012414144472216013216 0ustar liggesusersget_pass <- function(prompt = "Password: ") { askpass::askpass(prompt = prompt) } keyring/R/utils.R0000644000176200001440000000401414521152473013411 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.R0000644000176200001440000001015114521152473014425 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.R0000644000176200001440000004306114535445632015305 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 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 creating 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.R0000644000176200001440000001717114521152473014753 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.R0000644000176200001440000000754114521152473015354 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.R0000644000176200001440000000300314521152473014440 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.R0000644000176200001440000004561114521152473014565 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/keyring-package.R0000644000176200001440000000013514521172457015316 0ustar liggesusers#' @keywords internal "_PACKAGE" ## usethis namespace: start ## usethis namespace: end NULL keyring/R/backend-macos.R0000644000176200001440000001742214521152473014747 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.R0000644000176200001440000000402314521152473013644 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.R0000644000176200001440000002260414521152473013027 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.md0000644000176200001440000000342414535450147013033 0ustar liggesusers# keyring 1.3.2 * keyring uses safer `*printf()` format strings (Secret Service backend). # keyring 1.3.1 * No user visible changes. # 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/MD50000644000176200001440000000545714535455142012255 0ustar liggesusers4e0383bff4d65c7fc9e4b300f02dc9a4 *DESCRIPTION 81027c601e695aa6042185e19e463fbd *LICENSE 00556c6e3c95c8dcb180c6b8e6fb6933 *NAMESPACE d508e5bd4a04a4a306f7d5b755df2b09 *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 88aac3f32a9e98c8203a16cbde03c9fb *R/backend-wincred.R dcdf9ad591fd25f08c2e80897a5ab14a *R/default_backend.R df6cc46bc7fae1a55b713f3d5065b35a *R/keyring-package.R 38baf7dae344a876416a6b077ee33ad4 *R/package.R 3b218c5cb6c9fbf2a13ccd30f182c57a *R/pass.R 29f3016c1e412fd3862086fa5892d155 *R/utils.R 46f3a179ab7bdd864ef5b43b7ec217f0 *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 d5825658b5fa1e0ea31664395155d652 *man/b_wincred_get.Rd f1e5714dd9532c8e86ebe9076dcbb451 *man/b_wincred_set_with_raw_value.Rd 88544d5a25d963ba3df6cff70f5c32e8 *man/backend.Rd bf1451d1e551d3f771ad7efeb319c50c *man/backend_env.Rd 2932f4a9eeb8adebda591daab5af5ab0 *man/backend_file.Rd 0c749710ef7c1a9241b89b7054280595 *man/backend_keyrings.Rd 08751ee749884db7a9a83bcdf4949543 *man/backend_macos.Rd 5cedfc75f164b1d92d16d1ea74d4ed11 *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 dfcd1e46eb346bfe9cc816f755e30d8c *man/keyring-package.Rd 68b329da9893e34099c7d8ad5cb9c940 *src/Makevars.in d41d8cd98f00b204e9800998ecf8427e *src/Makevars.win 5ec155074ec9a440304b8f9555c7da28 *src/init.c fd01390e9fe19482453d9ef15ee8b607 *src/keyring_macos.c fa4c3dff4329a8ce55565ea86c2c4bc0 *src/keyring_secret_service.c 26bcbdde4c9090fd184fe4b0bc62730a *src/keyring_wincred.c c71b2828a99106971be998a78e899343 *tests/testthat.R ae825f5129036efff1a1298ffdbd2a84 *tests/testthat/helper.R 4de8b51a4e035534a51ed510e7f78c26 *tests/testthat/test-common.R 715a2d64f4a8d467bf2114fe095bdf0d *tests/testthat/test-default-backend.R 79971addfb1e90ae3937b095cb6a8dbe *tests/testthat/test-encoding.R eddfb41081b6a170b759c8d15459585b *tests/testthat/test-env.R 851e58dc876f22eefaca15cde4d93fb1 *tests/testthat/test-file.R 3e4f1c6b612db4d5dda0a8f5a45862bd *tests/testthat/test-macos.R daa33e5c49973769c2a00a6f0f96fb29 *tests/testthat/test-secret-service.R 851fa1ce6c5af5b22ea89cdaa4904f28 *tests/testthat/test-wincred.R keyring/inst/0000755000176200001440000000000014144472216012704 5ustar liggesuserskeyring/inst/development-notes.md0000644000176200001440000002256614144472216016711 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/cleanup0000755000176200001440000000004614535450210013276 0ustar liggesusers#!/usr/bin/env sh rm -f src/Makevars keyring/configure0000755000176200001440000000200414535450210013624 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