keyring/ 0000755 0001762 0000144 00000000000 14535455142 011732 5 ustar ligges users keyring/NAMESPACE 0000644 0001762 0000144 00000001417 14144472216 013151 0 ustar ligges users # 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/LICENSE 0000644 0001762 0000144 00000000055 14521172423 012730 0 ustar ligges users YEAR: 2023
COPYRIGHT HOLDER: keyring authors
keyring/README.md 0000644 0001762 0000144 00000007266 14521172457 013224 0 ustar ligges users
# keyring
[](https://github.com/r-lib/keyring/actions/workflows/R-CMD-check.yaml)
[](https://www.r-pkg.org/pkg/keyring)
[](https://www.r-pkg.org/pkg/keyring)
[](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/ 0000755 0001762 0000144 00000000000 14521172457 012505 5 ustar ligges users keyring/man/backend_wincred.Rd 0000644 0001762 0000144 00000002045 14144472216 016074 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000001501 14144472216 015537 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000002253 14144472216 020702 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000002431 14144472216 015230 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000001454 14144472216 016234 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000001152 14144472216 015356 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000003673 14521172457 016307 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000004522 14521172457 014366 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000004352 14144472216 014547 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000001620 14535445650 015571 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000006644 14535446175 016055 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000005651 14151122155 017067 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000002664 14521172457 017460 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000000717 14144472216 017265 0 ustar ligges users % 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.Rd 0000644 0001762 0000144 00000011045 14150723132 014412 0 ustar ligges users % 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/DESCRIPTION 0000644 0001762 0000144 00000003272 14535455142 013444 0 ustar ligges users Package: 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/ 0000755 0001762 0000144 00000000000 14521172457 013074 5 ustar ligges users keyring/tests/testthat/ 0000755 0001762 0000144 00000000000 14535455142 014734 5 ustar ligges users keyring/tests/testthat/test-wincred.R 0000644 0001762 0000144 00000005737 14521172457 017503 0 ustar ligges users test_that("low level API", {
skip_if_not_win()
skip_on_cran()
keyring <- random_keyring()
service <- random_service()
username <- random_username()
password <- random_password()
target <- b_wincred_target(keyring, service, username)
expect_false(b_wincred_i_exists(target))
expect_silent(b_wincred_i_set(target, charToRaw(password),
username, session = TRUE))
expect_true(b_wincred_i_exists(target))
expect_equal(rawToChar(b_wincred_i_get(target)), password)
expect_true(target %in% b_wincred_i_enumerate("*"))
expect_silent(b_wincred_i_delete(target))
expect_false(b_wincred_i_exists(target))
expect_false(target %in% b_wincred_i_enumerate("*"))
})
test_that("creating keychains", {
skip_if_not_win()
skip_on_cran()
keyring <- random_keyring()
kb <- backend_wincred$new(keyring = keyring)
kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret123!")
expect_true(keyring %in% kb$keyring_list()$keyring)
list <- kb$list()
expect_equal(nrow(list), 0)
service <- random_service()
username <- random_username()
password <- random_password()
expect_silent(
kb$set_with_value(service, username, password)
)
expect_equal(kb$get(service, username), password)
expect_silent(kb$delete(service, username))
expect_silent(kb$keyring_delete(keyring = keyring))
expect_false(keyring %in% kb$keyring_list()$keyring)
})
test_that("lock/unlock keyrings", {
skip_if_not_win()
skip_on_cran()
keyring <- random_keyring()
kb <- backend_wincred$new(keyring = keyring)
kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret123!")
## It is unlocked by default
expect_false(kb$keyring_is_locked())
list <- kb$keyring_list()
expect_true(keyring %in% list$keyring)
expect_false(list$locked[match(keyring, list$keyring)])
## Lock it
kb$keyring_lock()
expect_true(kb$keyring_is_locked())
list <- keyring_list()
expect_true(list$locked[match(keyring, list$keyring)])
## Unlock it
kb$keyring_unlock(password = "secret123!")
expect_false(kb$keyring_is_locked())
list <- kb$keyring_list()
expect_false(list$locked[match(keyring, list$keyring)])
expect_silent(kb$keyring_delete(keyring = keyring))
})
test_that(": in keyring, service and usernames", {
skip_if_not_win()
skip_on_cran()
keyring <- paste0("foo:", random_keyring())
service <- paste0("bar:", random_service())
username <- paste0("foobar:", random_username())
password <- random_password()
target <- b_wincred_target(keyring, service, username)
expect_false(b_wincred_i_exists(target))
expect_silent(b_wincred_i_set(target, charToRaw(password),
username, session = TRUE))
expect_true(b_wincred_i_exists(target))
expect_equal(rawToChar(b_wincred_i_get(target)), password)
expect_true(target %in% b_wincred_i_enumerate("*"))
expect_silent(b_wincred_i_delete(target))
expect_false(b_wincred_i_exists(target))
expect_false(target %in% b_wincred_i_enumerate("*"))
})
keyring/tests/testthat/test-env.R 0000644 0001762 0000144 00000001754 14521172457 016633 0 ustar ligges users test_that("set, get, delete", {
service <- random_service()
username <- random_username()
password <- random_password()
kb <- backend_env$new()
var <- kb$.__enclos_env__$private$env_to_var(service, username)
expect_silent(
kb$set_with_value(service, username, password)
)
expect_equal(kb$list(service)$username, c(username))
expect_equal(kb$get(service, username), password)
expect_equal(Sys.getenv(var, "foo"), password)
expect_silent(kb$delete(service, username))
expect_equal(Sys.getenv(var, "foo"), "foo")
})
test_that("set, get, delete, without username", {
service <- random_service()
password <- random_password()
kb <- backend_env$new()
var <- kb$.__enclos_env__$private$env_to_var(service, NULL)
expect_silent(
kb$set_with_value(service, password = password)
)
expect_equal(kb$get(service), password)
expect_equal(Sys.getenv(var, "foo"), password)
expect_silent(kb$delete(service))
expect_equal(Sys.getenv(var, "foo"), "foo")
})
keyring/tests/testthat/test-default-backend.R 0000644 0001762 0000144 00000004130 14521172457 021043 0 ustar ligges users opts <- options(keyring_warn_for_env_fallback = FALSE)
on.exit(options(opts), add = TRUE)
test_that("use options", {
withr::with_options(
list(keyring_backend = "env"),
expect_equal(default_backend(), backend_env$new())
)
withr::with_options(
list(keyring_backend = "env"),
expect_equal(default_backend(), backend_env$new())
)
## This should run on all OSes currently, as we are not actually
## calling the Keychain API here.
withr::with_options(
list(keyring_backend = "macos", keyring_keyring = "foobar"),
expect_equal(default_backend(), backend_macos$new(keyring = "foobar"))
)
})
test_that("use env var", {
## Remove the options
withr::with_options(
list(keyring_backend = NULL, keyring_keyring = NULL),
withr::with_envvar(
c(R_KEYRING_BACKEND = "env"),
expect_equal(default_backend(), backend_env$new())
)
)
})
test_that("mixing options and env vars", {
## Backend option, keyring env var
withr::with_options(
list(keyring_backend = "macos", keyring_keyring = NULL),
withr::with_envvar(
c(R_KEYRING_KEYRING = "foobar"),
expect_equal(default_backend(), backend_macos$new(keyring = "foobar"))
)
)
## Backend env var, keyring option
withr::with_options(
list(keyring_backend = NULL, keyring_keyring = "foobar"),
withr::with_envvar(
c(R_KEYRING_BACKEND = "macos"),
expect_equal(default_backend(), backend_macos$new(keyring = "foobar"))
)
)
})
test_that("auto windows", {
mockery::stub(default_backend_auto, "Sys.info", c(sysname = "Windows"))
expect_equal(default_backend_auto(), backend_wincred)
})
test_that("auto macos", {
mockery::stub(default_backend_auto, "Sys.info", c(sysname = "Darwin"))
expect_equal(default_backend_auto(), backend_macos)
})
test_that("auto linux", {
skip_if_not_linux()
kb <- default_backend()
expect_true(kb$name == "env" || kb$name == "secret service" || kb$name == "file")
})
test_that("auto other", {
mockery::stub(default_backend_auto, "Sys.info", c(sysname = "Solaris"))
expect_equal(suppressWarnings(default_backend_auto()), backend_file)
})
keyring/tests/testthat/test-secret-service.R 0000644 0001762 0000144 00000003720 14521172457 020761 0 ustar ligges users opts <- options(keyring_warn_for_env_fallback = FALSE)
on.exit(options(opts), add = TRUE)
test_that("specify keyring explicitly", {
skip_if_not_secret_service()
skip_on_cran()
service <- random_service()
username <- random_username()
password <- random_password()
kb <- backend_secret_service$new("Login")
expect_silent(
kb$set_with_value(service, username, password)
)
expect_equal(kb$get(service, username), password)
expect_silent(kb$delete(service, username))
})
test_that("creating keychains", {
skip("requires interaction")
skip_if_not_secret_service()
skip_on_cran()
keyring <- random_keyring()
kb <- backend_secret_service$new(keyring = keyring)
kb$keyring_create(keyring = keyring)
kb$keyring_list()
service <- random_service()
username <- random_username()
password <- random_password()
expect_silent(
kb$set_with_value(service, username, password)
)
expect_equal(kb$get(service, username), password)
expect_silent(kb$delete(service, username))
expect_silent(kb$keyring_delete(keyring = keyring))
expect_false(keyring %in% keyring_list()$keyring)
})
test_that("lock/unlock keyrings", {
skip("requires interaction")
skip_if_not_secret_service()
skip_on_cran()
keyring <- random_keyring()
kb <- backend_secret_service$new(keyring = keyring)
# interactive
kb$.__enclos_env__$private$keyring_create_direct(keyring)
## It is unlocked by default
expect_false(kb$keyring_is_locked())
list <- kb$keyring_list()
expect_true(keyring %in% list$keyring)
expect_false(list$locked[match(keyring, list$keyring)])
## Lock it
kb$keyring_lock()
expect_true(kb$keyring_is_locked())
list <- kb$keyring_list()
expect_true(list$locked[match(keyring, list$keyring)])
## Unlock it (interactive)
kb$keyring_unlock()
expect_false(kb$keyring_is_locked())
list <- keyring_list()
expect_false(list$locked[match(keyring, list$keyring)])
expect_silent(kb$keyring_delete(keyring = keyring))
})
keyring/tests/testthat/test-encoding.R 0000644 0001762 0000144 00000007724 14521172457 017634 0 ustar ligges users test_that("No option/env var set returns auto", {
skip_if_not_win()
withr::local_options(keyring.encoding_windows = NULL)
withr::local_envvar(KEYRING_ENCODING_WINDOWS = NA_character_)
encoding <- get_encoding_opt()
expect_equal(encoding, "auto")
})
test_that("Option encoding set and env unset returns option encoding", {
skip_if_not_win()
withr::local_options(keyring.encoding_windows = "UTF-16LE")
withr::local_envvar(KEYRING_ENCODING_WINDOWS = NA_character_)
encoding <- get_encoding_opt()
expect_equal(encoding, "UTF-16LE")
})
test_that("Option encoding unset and env set returns env encoding", {
skip_if_not_win()
withr::local_options(keyring.encoding_windows = NULL)
withr::local_envvar("KEYRING_ENCODING_WINDOWS" = "UTF-8")
encoding <- get_encoding_opt()
expect_equal(encoding, "UTF-8")
})
test_that("Option encoding set and env var set and EQUAL returns expected value", {
skip_if_not_win()
withr::local_options(keyring.encoding_windows = "UTF-16LE")
withr::local_envvar("KEYRING_ENCODING_WINDOWS" = "UTF-16LE")
encoding <- get_encoding_opt()
expect_equal(encoding, "UTF-16LE")
})
test_that("Invalid encoding (not in iconvlist) returns error", {
skip_if_not_win()
withr::local_options(keyring.encoding_windows = "doesnotexist")
withr::local_envvar("KEYRING_ENCODING_WINDOWS" = "doesnotexist")
expect_error(get_encoding_opt())
})
test_that("iconv suggestion works as expected", {
skip_if_not_win()
withr::local_options(keyring.encoding_windows = "UTF-16LP")
withr::local_envvar("KEYRING_ENCODING_WINDOWS" = NA_character_)
expect_error(
get_encoding_opt(),
"Encoding not found in iconvlist(). Did you mean UTF-16LE?",
fixed = TRUE
)
})
test_that("Option has precedence", {
skip_if_not_win()
withr::local_options(keyring.encoding_windows = iconvlist()[1])
withr::local_envvar("KEYRING_ENCODING_WINDOWS" = iconvlist()[2])
expect_identical(get_encoding_opt(), iconvlist()[1])
})
test_that("Set key with UTF-16LE encoding", {
skip_if_not_win()
skip_on_cran()
service <- random_service()
user <- random_username()
pass <- random_password()
# Now, set a key with UTF-16LE encoding using new options
withr::local_options(keyring.encoding_windows = NULL)
withr::local_envvar("KEYRING_ENCODING_WINDOWS" = "UTF-16LE")
keyring::key_set_with_value(service = service, username = user, password = pass)
# Get the password
expect_equal(keyring::key_get(service = service, username = user), pass)
# Show that it is UTF-16LE
raw_password <- keyring:::b_wincred_i_get(target = paste0(":", service, ":", user))
expect_equal(iconv(list(raw_password), from = "UTF-16LE", to = ""), pass)
key_delete(service = service, username = user)
})
test_that("Set key with UTF-16LE encoding plus a keyring", {
skip_if_not_win()
skip_on_cran()
withr::local_options(keyring.encoding_windows = NULL)
withr::local_envvar("KEYRING_ENCODING_WINDOWS" = "UTF-16LE")
keyring <- random_keyring()
kb <- backend_wincred$new(keyring = keyring)
kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret123!")
expect_true(keyring %in% kb$keyring_list()$keyring)
list <- kb$list()
expect_equal(nrow(list), 0)
service <- random_service()
username <- random_username()
password <- random_password()
expect_silent(
kb$set_with_value(service, username, password)
)
expect_equal(kb$get(service, username), password)
expect_silent(kb$delete(service, username))
expect_silent(kb$keyring_delete(keyring = keyring))
expect_false(keyring %in% kb$keyring_list()$keyring)
})
test_that("marked UTF-8 strings work", {
skip_if_not_win()
skip_on_cran()
withr::local_options(keyring.encoding_windows = "UTF-8")
service <- random_service()
user <- random_username()
pass <- "this is ok: \u00bc"
keyring::key_set_with_value(service = service, username = user, password = pass)
# Get the password
expect_equal(keyring::key_get(service = service, username = user), pass)
key_delete(service = service, username = user)
})
keyring/tests/testthat/test-file.R 0000644 0001762 0000144 00000020345 14521172457 016757 0 ustar ligges users test_that("specify keyring explicitly", {
dir.create(tmp <- tempfile())
on.exit(unlink(tmp, recursive = TRUE))
withr::local_options(list(keyring_file_dir = tmp))
service_1 <- random_service()
username <- random_username()
password <- random_password()
password2 <- random_password()
keyring <- random_keyring()
kb <- backend_file$new(keyring = keyring)
kb$keyring_create(keyring, "secret123!")
expect_false(kb$keyring_is_locked(keyring))
kb$keyring_lock(keyring)
expect_true(kb$keyring_is_locked(keyring))
expect_silent(kb$keyring_unlock(keyring, password = "secret123!"))
expect_false(kb$keyring_is_locked(keyring))
## Missing
expect_error(kb$get(service_1, username), "could not be found")
expect_silent(kb$set_with_value(service_1, username, password))
expect_equal(kb$get(service_1, username), password)
## Missing
expect_error(kb$get(service_1, "foobar"), "could not be found")
## Overwrite
expect_silent(kb$set_with_value(service_1, username, password2))
expect_equal(kb$get(service_1, username), password2)
expect_silent(kb$set_with_value(random_service(), username, password))
long_password <- random_string(500L)
service_2 <- random_service()
expect_silent(kb$set_with_value(service_2, username, long_password))
expect_equal(kb$get(service_2, username), long_password)
## Delete
expect_silent(kb$delete(service_1, username))
expect_error(kb$get(service_1, username), "could not be found")
## Delete non-existent is silent
expect_silent(kb$delete(service_1, username))
expect_silent(kb$keyring_delete())
})
test_that("key consistency check", {
username <- random_username()
password <- random_password()
keyring <- random_keyring()
dir.create(tmp <- tempfile())
on.exit(unlink(tmp, recursive = TRUE), add = TRUE)
withr::local_options(list(keyring_file_dir = tmp))
keyring_pwd_1 <- random_password()
keyring_pwd_2 <- random_password()
kb <- backend_file$new(keyring = keyring)
kb$keyring_create(keyring, keyring_pwd_1)
expect_silent(kb$keyring_unlock(password = keyring_pwd_1))
expect_silent(kb$set_with_value(random_service(), username, password))
expect_error(kb$keyring_unlock(password = keyring_pwd_2),
"cannot unlock keyring")
expect_silent(kb$keyring_lock())
expect_error(kb$keyring_unlock(password = keyring_pwd_2),
"cannot unlock keyring")
kb$.__enclos_env__$private$set_keyring_pass(keyring_pwd_2)
expect_true(kb$keyring_is_locked())
kb$.__enclos_env__$private$unset_keyring_pass()
# with_mock(`keyring:::get_pass` = mockery::mock(keyring_pwd_1), {
# expect_silent(kb$set_with_value(random_service(), username, password))
# })
expect_silent(kb$keyring_delete())
})
test_that("use non-default keyring", {
dir.create(tmp <- tempfile())
on.exit(unlink(tmp, recursive = TRUE), add = TRUE)
withr::local_options(list(keyring_file_dir = tmp))
service <- random_service()
username <- random_username()
password <- random_password()
default_keyring <- random_keyring()
keyring <- random_keyring()
default_keyring_pwd <- random_password()
keyring_pwd <- random_password()
kb <- backend_file$new(keyring = default_keyring)
kb$keyring_create(password = default_keyring_pwd)
kb$keyring_create(keyring, keyring_pwd)
expect_silent(kb$keyring_unlock(password = default_keyring_pwd))
expect_false(kb$keyring_is_locked())
expect_silent(kb$keyring_unlock(keyring, keyring_pwd))
expect_false(kb$keyring_is_locked(keyring))
expect_false(kb$keyring_is_locked())
expect_silent(kb$set_with_value(service, username, password, keyring))
expect_equal(kb$get(service, username, keyring), password)
expect_silent(
all_items <- kb$list(keyring = keyring)
)
expect_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.R 0000644 0001762 0000144 00000002464 14521152473 016340 0 ustar ligges users
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.R 0000644 0001762 0000144 00000007534 14521172457 017147 0 ustar ligges users test_that("specify keyring explicitly", {
skip_if_not_macos()
skip_on_cran()
service <- random_service()
username <- random_username()
password <- random_password()
kb <- backend_macos$new(keyring = "login")
expect_silent(
kb$set_with_value(service, username, password)
)
expect_equal(kb$get(service, username), password)
expect_silent(kb$delete(service, username))
})
test_that("creating keychains", {
skip_if_not_macos()
skip_on_cran()
keyring <- random_keyring()
kb <- backend_macos$new()
## To avoid an interactive password
kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret123!")
list <- kb$list(keyring = keyring)
expect_equal(nrow(list), 0)
service <- random_service()
username <- random_username()
password <- random_password()
expect_silent(
kb$set_with_value(service, username, password)
)
expect_equal(kb$get(service, username), password)
expect_silent(kb$delete(service, username))
expect_silent(kb$keyring_delete(keyring = keyring))
expect_false(keyring %in% kb$keyring_list()$keyring)
})
test_that("creating keychains 2", {
skip_if_not_macos()
skip_on_cran()
keyring <- random_keyring()
kb <- backend_macos$new()
## To avoid an interactive password
kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret")
kb$keyring_set_default(keyring)
service <- random_service()
username <- random_username()
password <- random_password()
expect_silent(
kb$set_with_value(service, username, password)
)
expect_equal(kb$get(service, username), password)
expect_silent(kb$delete(service, username))
expect_silent(kb$keyring_delete(keyring = keyring))
expect_false(keyring %in% kb$keyring_list()$keyring)
})
test_that("keyring file at special location", {
skip_if_not_macos()
skip_on_cran()
keyring <- tempfile(fileext = ".keychain")
kb <- backend_macos$new(keyring = keyring)
kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret123!")
service <- random_service()
username <- random_username()
password <- random_password()
expect_silent(
kb$set_with_value(service, username, password)
)
expect_equal(kb$get(service, username), password)
expect_silent(kb$delete(service, username))
expect_silent(kb$keyring_delete(keyring = keyring))
expect_false(keyring %in% kb$keyring_list()$keyring)
expect_false(file.exists(keyring))
})
test_that("errors", {
skip_if_not_macos()
skip_on_cran()
## Non-existing keychain
expect_error(
backend_macos$new(tempfile())$list(),
"cannot open keychain"
)
## Getting non-existing password
expect_error(
backend_macos$new()$get(random_service(), random_username()),
"cannot get password"
)
## Deleting non-existing password
expect_error(
backend_macos$new()$delete(random_service(), random_username()),
"cannot delete password"
)
## Create keychain without access to file
kb <- backend_macos$new()
expect_error(
kb$.__enclos_env__$private$keyring_create_direct("/xxx", "secret123!"),
"cannot create keychain"
)
})
test_that("lock/unlock keyrings", {
skip_if_not_macos()
skip_on_cran()
keyring <- random_keyring()
kb <- backend_macos$new(keyring = keyring)
kb$.__enclos_env__$private$keyring_create_direct(keyring, "secret123!")
## It is unlocked by default
expect_false(kb$keyring_is_locked())
list <- kb$keyring_list()
expect_true(keyring %in% list$keyring)
expect_false(list$locked[match(keyring, list$keyring)])
## Lock it
kb$keyring_lock()
expect_true(kb$keyring_is_locked())
list <- kb$keyring_list()
expect_true(list$locked[match(keyring, list$keyring)])
## Unlock it
kb$keyring_unlock(password = "secret123!")
expect_false(kb$keyring_is_locked())
list <- kb$keyring_list()
expect_false(list$locked[match(keyring, list$keyring)])
expect_silent(kb$keyring_delete(keyring = keyring))
})
keyring/tests/testthat/test-common.R 0000644 0001762 0000144 00000003725 14521172457 017333 0 ustar ligges users withr::local_options(
keyring_warn_for_env_fallback = FALSE,
keyring_file_dir = file.path(tempdir(), "keyrings")
)
on.exit(unlink(file.path(tempdir(), "keyrings"), recursive = TRUE), add = TRUE)
# The file backend needs a default keyring currently
kb <- default_backend()
if (kb$name == "file") {
if (! "system" %in% kb$keyring_list()$keyring) {
kb$.__enclos_env__$private$keyring_create_direct("system", "master")
}
kb$keyring_unlock("system", "master")
}
test_that("set, get, delete", {
skip_on_cran()
service <- random_service()
username <- random_username()
password <- random_password()
expect_silent(key_set_with_value(service, username, password))
expect_equal(key_get(service, username), password)
expect_silent(key_delete(service, username))
})
test_that("set, get, delete without username", {
skip_on_cran()
service <- random_service()
password <- random_password()
expect_silent(key_set_with_value(service, password = password))
expect_equal(key_get(service), password)
expect_silent(key_delete(service))
})
test_that("set can update", {
skip_on_cran()
service <- random_service()
username <- random_username()
password <- random_password()
expect_silent({
key_set_with_value(service, username, "foobar")
key_set_with_value(service, username, password)
})
expect_equal(key_get(service, username), password)
expect_silent(key_delete(service, username))
})
test_that("list", {
skip_on_cran()
if (default_backend()$name == "env") skip("'env' backend has no 'list' support")
service <- random_service()
username <- random_username()
password <- random_password()
expect_silent({
key_set_with_value(service, username, password)
list <- key_list()
})
expect_equal(list$username[match(service, list$service)], username)
list2 <- key_list(service = service)
expect_equal(nrow(list2), 1)
expect_equal(list2$username, username)
expect_silent(key_delete(service, username))
})
keyring/tests/testthat.R 0000644 0001762 0000144 00000000612 14521172457 015056 0 ustar ligges users # 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/ 0000755 0001762 0000144 00000000000 14535450207 012516 5 ustar ligges users keyring/src/init.c 0000644 0001762 0000144 00000007474 14322212134 013626 0 ustar ligges users
#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.c 0000644 0001762 0000144 00000040444 14535445207 017431 0 ustar ligges users
/* 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.c 0000644 0001762 0000144 00000044277 14326440535 015533 0 ustar ligges users
#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.win 0000644 0001762 0000144 00000000000 14144472216 014774 0 ustar ligges users keyring/src/Makevars.in 0000644 0001762 0000144 00000000001 14326440532 014604 0 ustar ligges users
keyring/src/keyring_wincred.c 0000644 0001762 0000144 00000010315 14322212134 016032 0 ustar ligges users
/* 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.win 0000755 0001762 0000144 00000000001 14144472216 014421 0 ustar ligges users
keyring/R/ 0000755 0001762 0000144 00000000000 14521172457 012133 5 ustar ligges users keyring/R/backend-secret-service.R 0000644 0001762 0000144 00000016763 14521152473 016577 0 ustar ligges users
#' 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.R 0000644 0001762 0000144 00000000124 14144472216 013216 0 ustar ligges users get_pass <- function(prompt = "Password: ") {
askpass::askpass(prompt = prompt)
}
keyring/R/utils.R 0000644 0001762 0000144 00000004014 14521152473 013411 0 ustar ligges users
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.R 0000644 0001762 0000144 00000010151 14521152473 014425 0 ustar ligges users
#' 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.R 0000644 0001762 0000144 00000043061 14535445632 015305 0 ustar ligges users
## 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.R 0000644 0001762 0000144 00000017171 14521152473 014753 0 ustar ligges users
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.R 0000644 0001762 0000144 00000007541 14521152473 015354 0 ustar ligges users
#' 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.R 0000644 0001762 0000144 00000003003 14521152473 014440 0 ustar ligges users
#' @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.R 0000644 0001762 0000144 00000045611 14521152473 014565 0 ustar ligges users
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.R 0000644 0001762 0000144 00000000135 14521172457 015316 0 ustar ligges users #' @keywords internal
"_PACKAGE"
## usethis namespace: start
## usethis namespace: end
NULL
keyring/R/backend-macos.R 0000644 0001762 0000144 00000017422 14521152473 014747 0 ustar ligges users
#' 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.R 0000644 0001762 0000144 00000004023 14521152473 013644 0 ustar ligges users
#' 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.R 0000644 0001762 0000144 00000022604 14521152473 013027 0 ustar ligges users
#' 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.md 0000644 0001762 0000144 00000003424 14535450147 013033 0 ustar ligges users # 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/MD5 0000644 0001762 0000144 00000005457 14535455142 012255 0 ustar ligges users 4e0383bff4d65c7fc9e4b300f02dc9a4 *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/ 0000755 0001762 0000144 00000000000 14144472216 012704 5 ustar ligges users keyring/inst/development-notes.md 0000644 0001762 0000144 00000022566 14144472216 016711 0 ustar ligges users
# 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/cleanup 0000755 0001762 0000144 00000000046 14535450210 013276 0 ustar ligges users #!/usr/bin/env sh
rm -f src/Makevars
keyring/configure 0000755 0001762 0000144 00000002004 14535450210 013624 0 ustar ligges users #!/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