Skip to content

Commit

Permalink
Move vault to inst/vault
Browse files Browse the repository at this point in the history
  • Loading branch information
hadley committed Oct 21, 2014
1 parent 5f7c4e1 commit 705eb66
Show file tree
Hide file tree
Showing 13 changed files with 97 additions and 70 deletions.
1 change: 0 additions & 1 deletion .Rbuildignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
^.*\.Rproj$
^\.Rproj\.user$
^secure$
^\.travis\.yml$
47 changes: 25 additions & 22 deletions R/encryption.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
#'
#' @param .name,name Name of storage locker.
#' @param ... Name-value pairs of objects to store.
#' @param .pkg,pkg Path to package. Defaults to current directory.
#' @param .vault,vault Name of secure vault. If \code{NULL} looks for
#' \code{vault} or \code{inst/vault} in the current directory. If a string,
#' looks for a secure vault in the package with that name
#' @export
#' @examples
#' \dontrun{
Expand All @@ -12,16 +14,16 @@
#'
#' decrypt("test")
#' }
encrypt <- function(.name, ..., .pkg = ".") {
pkg <- as.package(pkg)
encrypt <- function(.name, ..., .vault = NULL) {
vault <- find_vault(.vault)
key <- my_key()

values <- list(...)
path <- locker_path(.name, pkg)
path <- locker_path(.name, vault)

if (file.exists(path)) {
message("Merging with existing data")
old_values <- decrypt(basename(path), pkg = pkg)
old_values <- decrypt(basename(path), vault = vault)
values <- modifyList(old_values, values)
}

Expand All @@ -32,11 +34,11 @@ encrypt <- function(.name, ..., .pkg = ".") {

#' @rdname encrypt
#' @export
decrypt <- function(name, pkg = ".") {
pkg <- as.package(pkg)
key <- my_key(pkg = pkg)
decrypt <- function(name, vault = NULL) {
vault <- find_vault(vault)
key <- my_key(vault = vault)

path <- locker_path(name, pkg)
path <- locker_path(name, vault)
if (!file.exists(path)) {
stop(path, " does not exist", call. = FALSE)
}
Expand All @@ -47,19 +49,18 @@ decrypt <- function(name, pkg = ".") {
unserialize(dec)
}

locker_path <- function(name, pkg = ".") {
locker_path <- function(name, vault) {
stopifnot(is.character(name), length(name) == 1)
pkg <- as.package(pkg)
vault <- find_vault(vault)

if (!grepl("\\.rds.enc", name)) {
name <- paste0(name, ".rds.enc")
}

file.path(pkg$vault, name)
file.path(vault, name)
}

my_key <- function(key = local_key(), pkg = ".") {
pkg <- as.package(pkg)
my_key <- function(key = local_key(), vault = NULL) {
vault <- find_vault(vault)
# Travis needs a slightly different strategy because we can't access the
# private key - instead we let travis encrypt the key in an env var
if (is_travis()) {
Expand All @@ -69,7 +70,7 @@ my_key <- function(key = local_key(), pkg = ".") {
der <- PKI::PKI.save.key(key, "DER")
same_key <- function(x) identical(PKI::PKI.save.key(x$public_key, "DER"), der)

me <- Filter(same_key, load_users(pkg))
me <- Filter(same_key, load_users(vault))
if (length(me) == 0) {
stop("No user matches public key ", format(key), call. = FALSE)
} else if (length(me) > 1) {
Expand All @@ -90,12 +91,14 @@ is_travis <- function() {
#' This ensures that we can find your private key, and you can decrypt
#' the encrypted master key.
#'
#' @param pkg Path to package with secure vault
#' @param vault Name of secure vault. If \code{NULL} looks for
#' \code{vault} or \code{inst/vault} in the current directory. If a string,
#' looks for a secure vault in the package with that name
#' @return A boolean flag.
#' @export
has_key <- function(pkg = ".") {
has_key <- function(vault = NULL) {
tryCatch({
my_key(pkg = pkg)
my_key(vault = vault)
TRUE
}, error = function(e) FALSE)
}
Expand All @@ -107,14 +110,14 @@ has_key <- function(pkg = ".") {
#' assets. Skipped tests do not generate an error in R CMD check etc, but
#' will print a visible notification.
#'
#' @param pkg Path to package with secure vault
#' @inheritParams has_key
#' @export
skip_when_missing_key <- function(pkg = ".") {
skip_when_missing_key <- function(vault = NULL) {

if (!requireNamespace("testthat", quietly = TRUE)) {
stop("testthat not installed", call. = FALSE)
}

if (has_key(pkg)) return()
if (has_key(vault)) return()
testthat::skip("Credentials to unlock secure files not available.")
}
34 changes: 26 additions & 8 deletions R/package.R
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
as.package <- function(path = ".") {
if (is.package(path)) return(path)
find_vault <- function(x = NULL) {
if (is.path(x)) {
return(x)
}

check_dir(path)
if (is.null(x)) {
# Assume you want the current directory
if (file.exists("vault")) {
path <- "vault"
} else if (file.exists("inst/vault")) {
path <- "inst/vault"
} else {
stop("Can't find vault/ or inst/vault in working directory",
call. = FALSE)
}

vault_path <- file.path(path, "vault")
check_dir(vault_path)
} else if (is.character(x)) {
# Name of a package
path <- system.file("vault", package = x)
if (identical(path, "")) {
stop(x, " does not contain secure vault (inst/vault).", call. = FALSE)
}
} else {
stop("Unknown input", call. = FALSE)
}

structure(list(path = path, vault = vault_path), class = "package")
check_dir(path)
structure(path, class = "path")
}

is.package <- function(x) inherits(x, "package")

is.path <- function(x) inherits(x, "path")

check_dir <- function(path) {
if (!file.exists(path)) {
Expand Down
5 changes: 2 additions & 3 deletions R/setup.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@ use_secure <- function(pkg = ".") {

pkg <- devtools::as.package(pkg)

secure_path <- file.path(pkg$path, "vault")
dir.create(secure_path, showWarnings = FALSE)
secure_path <- file.path(pkg$path, "inst", "vault")
dir.create(secure_path, showWarnings = FALSE, recursive = TRUE)

devtools::use_build_ignore("vault", pkg = pkg)
devtools::use_package("secure", "Suggests", pkg = pkg)

invisible(TRUE)
Expand Down
46 changes: 23 additions & 23 deletions R/users.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#' @param name Name of user. Currently only used to help you remember who
#' owns the public key
#' @param public_key Users public key.
#' @param pkg Path to package. Defaults to working directory
#' @inheritParams has_key
#' @export
#' @examples
#' \dontrun{
Expand All @@ -22,44 +22,44 @@
#' add_user("travis", travis_key("hadley/secure"))
#' remove_user("travis")
#' }
add_user <- function(name, public_key, pkg = ".") {
pkg <- as.package(pkg)
add_user <- function(name, public_key, vault = NULL) {
vault <- find_vault(vault)
stopifnot(inherits(public_key, "public.key"))

new_user <- list(name = name, public_key = public_key)
users <- c(load_users(pkg), list(new_user))
save_users(users, pkg = pkg)
users <- c(load_users(vault), list(new_user))
save_users(users, vault = vault)

recrypt(pkg)
recrypt(vault)
}

#' @rdname add_user
#' @export
remove_user <- function(name, pkg = ".") {
pkg <- as.package(pkg)
users <- load_users(pkg)
remove_user <- function(name, vault = ".") {
vault <- find_vault(vault)
users <- load_users(vault)

matching <- vapply(users, function(x) identical(x$name, name), logical(1))
if (!any(matching)) {
stop("Could not find user called ", name, call. = FALSE)
}

save_users(users[!matching], pkg = pkg)
recrypt(pkg)
save_users(users[!matching], vault = vault)
recrypt(vault)
}

recrypt <- function(pkg, key = new_key()) {
recrypt <- function(vault, key = new_key()) {
message("Re-encrypting all files with new key")
pkg <- as.package(pkg)
old_key <- my_key()
vault <- find_vault(vault)
old_key <- my_key(vault = vault)

# Encrypt new password for each user
users <- load_users(pkg)
users <- load_users(vault)
users <- lapply(users, recrypt_user, key = key)
save_users(users, pkg = pkg)
save_users(users, vault = vault)

# Decrypt & reencrypt each file
files <- dir(pkg$vault, "\\.rds\\.enc$", full.names = TRUE)
files <- dir(vault, "\\.rds\\.enc$", full.names = TRUE)
lapply(files, recrypt_file, old_key = old_key, new_key = key)

invisible(TRUE)
Expand Down Expand Up @@ -89,10 +89,10 @@ recrypt_file <- function(path, old_key, new_key) {
writeBin(enc, path)
}

load_users <- function(pkg) {
pkg <- as.package(pkg)
load_users <- function(vault) {
vault <- find_vault(vault)

path <- file.path(pkg$vault, "users.json")
path <- file.path(vault, "users.json")
if (!file.exists(path)) {
users <- list()
} else {
Expand All @@ -105,15 +105,15 @@ load_users <- function(pkg) {
})
}

save_users <- function(users, pkg) {
pkg <- as.package(pkg)
save_users <- function(users, vault) {
vault <- find_vault(vault)
users <- lapply(users, function(x) {
x$name <- jsonlite::unbox(x$name)
x$public_key <- PKI::PKI.save.key(x$public_key, "PEM")
x
})

path <- file.path(pkg$vault, "users.json")
path <- file.path(vault, "users.json")
writeLines(jsonlite::toJSON(users, pretty = TRUE), path)
invisible(TRUE)
}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ devtools::install_github("hadley/secure")

To get started, run `secure::use_secure()` in a package working directory. This will:

* Create a `vault/` directory.
* Create a `inst/vault/` directory.
* Add it to `.Rbuildignore`.
* Add secure to the `Suggests` field in `DESCRIPTION`.

Expand Down
File renamed without changes.
File renamed without changes.
8 changes: 5 additions & 3 deletions man/add_user.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@
\alias{remove_user}
\title{Add and remove users user.}
\usage{
add_user(name, public_key, pkg = ".")
add_user(name, public_key, vault = NULL)

remove_user(name, pkg = ".")
remove_user(name, vault = ".")
}
\arguments{
\item{name}{Name of user. Currently only used to help you remember who
owns the public key}

\item{public_key}{Users public key.}

\item{pkg}{Path to package. Defaults to working directory}
\item{vault}{Name of secure vault. If \code{NULL} looks for
\code{vault} or \code{inst/vault} in the current directory. If a string,
looks for a secure vault in the package with that name}
}
\description{
Adding or removing users will re-generate the master key and re-encrypt all
Expand Down
8 changes: 5 additions & 3 deletions man/encrypt.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
\alias{encrypt}
\title{Encrypt and decrypt data.}
\usage{
encrypt(.name, ..., .pkg = ".")
encrypt(.name, ..., .vault = NULL)

decrypt(name, pkg = ".")
decrypt(name, vault = NULL)
}
\arguments{
\item{.name,name}{Name of storage locker.}

\item{...}{Name-value pairs of objects to store.}

\item{.pkg,pkg}{Path to package. Defaults to current directory.}
\item{.vault,vault}{Name of secure vault. If \code{NULL} looks for
\code{vault} or \code{inst/vault} in the current directory. If a string,
looks for a secure vault in the package with that name}
}
\description{
Encrypt and decrypt data.
Expand Down
6 changes: 4 additions & 2 deletions man/has_key.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
\alias{has_key}
\title{Can you unlock the secure storage?}
\usage{
has_key(pkg = ".")
has_key(vault = NULL)
}
\arguments{
\item{pkg}{Path to package with secure vault}
\item{vault}{Name of secure vault. If \code{NULL} looks for
\code{vault} or \code{inst/vault} in the current directory. If a string,
looks for a secure vault in the package with that name}
}
\value{
A boolean flag.
Expand Down
6 changes: 4 additions & 2 deletions man/skip_when_missing_key.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
\alias{skip_when_missing_key}
\title{Skip tests when you can't unlock}
\usage{
skip_when_missing_key(pkg = ".")
skip_when_missing_key(vault = NULL)
}
\arguments{
\item{pkg}{Path to package with secure vault}
\item{vault}{Name of secure vault. If \code{NULL} looks for
\code{vault} or \code{inst/vault} in the current directory. If a string,
looks for a secure vault in the package with that name}
}
\description{
This is useful to place at the top of tests that rely on access to secured
Expand Down
4 changes: 2 additions & 2 deletions tests/testthat/test-decrypt.R
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
context("decrypt")

test_that("can decrypt secrets", {
skip_when_missing_key("../..")
skip_when_missing_key("secure")

test <- decrypt("test", pkg = "../..")
test <- decrypt("test", vault = "secure")
expect_equal(test$a, 1)
expect_equal(test$b, 2)
})

0 comments on commit 705eb66

Please sign in to comment.