test_that("missing user key throws error", { expect_error(data_load_keypair_user(tempfile()), "key does not exist") }) test_that("load and reload openssl keypair", { pair <- data_load_keypair_user("pair1") expect_s3_class(pair, "cyphr_keypair") expect_identical(data_load_keypair_user(pair), pair) }) test_that("require openssl keypair", { pair <- structure(list(type = "sodium"), class = "cyphr_keypair") expect_error(data_load_keypair_user(pair), "Expected an 'openssl' keypair") }) test_that("user keypair invalid input", { expect_error(data_load_keypair_user(1), "Invalid input for 'path_user'") }) test_that("initialisation requires existing directory", { expect_error(data_admin_init(tempfile(), "pair1"), "'path_data' must exist and be a directory") path <- tempfile() writeLines(character(0), path) on.exit(file.remove(path)) expect_error(data_admin_init(path, "pair1"), "'path_data' must exist and be a directory") }) test_that("initialisation", { path <- tempfile() dir.create(path, FALSE, TRUE) quiet <- FALSE res <- suppressMessages(data_admin_init(path, "pair1", quiet)) expect_s3_class(res, "cyphr_key") expect_true(file.exists(data_path_cyphr(path))) expect_true(file.exists(data_path_test(path))) expect_identical(decrypt_string(data_path_test(path), res), "cyphr") keys <- data_admin_list_keys(path) expect_equal(length(keys), 1L) expect_s3_class(keys, "data_keys") expect_identical(keys[[1]]$pub, data_load_keypair_user("pair1")$pub) msg <- capture_messages(data_request_access(path, "pair1")) expect_match(msg, "You appear to already have access") msg <- capture_messages(data_admin_init(path, "pair1", quiet)) expect_match(msg[[1]], "Already set up") }) test_that("grant access", { path <- tempfile() dir.create(path, FALSE, TRUE) quiet <- FALSE res <- data_admin_init(path, "pair1", quiet = TRUE) r <- runif(10) encrypt_object(r, res, file.path(path, "data.rds")) h <- data_request_access(path, "pair2", quiet = TRUE) msg <- capture_messages(h2 <- data_request_access(path, "pair2", quiet)) expect_match(msg[[1]], "already pending") expect_identical(h2, h) pair2 <- data_load_keypair_user("pair2") expect_identical(h, data_key_fingerprint(pair2$pub, data_schema_version())) ## This is the request: path_req <- file.path(data_path_request(path), bin2str(h, "")) expect_true(file.exists(path_req)) dat_req <- readRDS(path_req) expect_identical(dat_req$pub, pair2$pub) expect_identical(dat_req$host, Sys.info()[["nodename"]]) expect_identical(dat_req$user, Sys.info()[["user"]]) expect_s3_class(dat_req$date, "POSIXt") ## Try loading requests: expect_message(keys <- data_load_request(path, NULL, quiet), "There is 1 request for access") tmp <- keys[[1]] tmp$filename <- NULL expect_identical(tmp, dat_req) expect_identical(data_load_request(path, h), keys) expect_identical(data_load_request(path, bin2str(h)), keys) expect_identical(data_load_request(path, bin2str(h, "")), keys) ## What about nonexistant requests? expect_error(data_load_request(path, paste(rep("a", 32), collapse = "")), "No key 'a+' found at path") expect_error(data_load_request(path, TRUE), "Invalid type for 'hash'") ans <- data_admin_authorise(path, h, "pair1", TRUE, quiet = TRUE) ## Then the new user can connect: k <- data_key(path, "pair2") expect_s3_class(k, "cyphr_key") expect_identical(k$key(), res$key()) expect_identical(decrypt_object(file.path(path, "data.rds"), k), r) }) test_that("git messages", { path <- tempfile() dir.create(path, FALSE, TRUE) dir.create(file.path(path, ".git"), FALSE, TRUE) quiet <- FALSE res <- data_admin_init(path, "pair1", quiet = TRUE) res <- testthat::evaluate_promise(data_request_access(path, "pair2", quiet)) expect_match(res$messages, "If you are using git", all = FALSE) res <- evaluate_promise( data_admin_authorise(path, res$value, "pair1", TRUE, quiet)) expect_match(res$messages, "If you are using git", all = FALSE) }) test_that("not set up", { path <- tempfile() dir.create(path, FALSE, TRUE) expect_error(data_key(path, "pair2"), "cyphr not set up for") expect_error( with_dir(path, data_key(NULL, "pair2")), "cyphr not set up for") expect_error( with_dir(path, data_key(path_user = "pair2")), "cyphr not set up for") res <- data_admin_init(path, "pair1", quiet = TRUE) expect_error(data_key(path, "pair2"), "Key file not found; you may not have access") }) test_that("authorise no keys", { path <- tempfile() dir.create(path, FALSE, TRUE) res <- data_admin_init(path, "pair1", quiet = TRUE) msg <- capture_messages(data_admin_authorise(path, path_user = "pair1")) expect_match(msg[[1]], "There is 0 requests for access") expect_match(msg[[2]], "No keys to add") }) test_that("cancel auth", { path <- tempfile() dir.create(path, FALSE, TRUE) res <- data_admin_init(path, "pair1", quiet = TRUE) h <- data_request_access(path, "pair2", quiet = TRUE) testthat::with_mocked_bindings( prompt_confirm = function() FALSE, { msg <- capture_messages( try(data_admin_authorise(path, h, "pair1", FALSE), silent = TRUE)) expect_match(msg, "Cancelled adding key", all = FALSE) expect_error( suppressMessages(data_admin_authorise(path, h, "pair1", quiet = TRUE)), "Errors adding 1 key") }) }) test_that("print keys", { path <- tempfile() dir.create(path, FALSE, TRUE) res <- data_admin_init(path, "pair1", quiet = TRUE) expect_output(print(data_admin_list_requests(path)), "(empty)", fixed = TRUE) msg <- capture.output(print(data_admin_list_keys(path))) expect_match(msg[[1]], "1 key:") h <- data_request_access(path, "pair2", quiet = TRUE) ans <- data_admin_authorise(path, h, "pair1", TRUE, quiet = TRUE) msg <- capture.output(print(data_admin_list_keys(path))) expect_match(msg[[1]], "2 keys:") }) test_that("detect tampering", { path <- tempfile() dir.create(path, FALSE, TRUE) res <- data_admin_init(path, "pair1", quiet = TRUE) ## Here's the request: h <- data_request_access(path, "pair2", quiet = TRUE) ## Here's the attacker: pair3 <- data_load_keypair_user("pair3") path_req <- data_path_request(path) path_use <- file.path(path_req, bin2str(h, "")) expect_true(file.exists(path_use)) dat <- readRDS(path_use) ## Try adding our own key here: dat$pub <- pair3$pub saveRDS(dat, path_use) expect_error(data_admin_authorise(path, h, "pair1", TRUE, quiet), "Public key hash disagrees for") }) test_that("decryption failed gives reasonable error", { path1 <- tempfile() path2 <- tempfile() dir.create(path1, FALSE, TRUE) dir.create(path2, FALSE, TRUE) res1 <- data_admin_init(path1, "pair1", quiet = TRUE) res2 <- data_admin_init(path2, "pair1", quiet = TRUE) file.copy(data_path_test(path1), data_path_test(path2), overwrite = TRUE) expect_error(data_key(path2, "pair1"), "Decryption failed") }) test_that("gracefully fail to initialise", { path <- tempfile() dir.create(path, FALSE, TRUE) testthat::with_mocked_bindings( data_authorise_write = function(...) stop("Unexplained error"), { msg <- capture_messages( try(data_admin_init(path, "pair1"), silent = TRUE)) expect_match(msg, "Removing data key", all = FALSE) }) expect_equal(dir(data_path_cyphr(path), all.files = TRUE, no.. = TRUE), character(0)) }) ## This set of tests verifies that if we call the data functions ## (except for init) we can use a subdirectory of the cyphr ## directories without a problem. test_that("Work from a subdirectory", { path <- tempfile() dir.create(path, FALSE, TRUE) res <- data_admin_init(path, "pair1", quiet = TRUE) sub <- file.path(path, "a/b/c") dir.create(sub, FALSE, TRUE) ## Need full paths to keys as they will be in a surprising location ## otherwise. pair1 <- normalizePath("pair1") pair2 <- normalizePath("pair2") h <- with_dir(sub, data_request_access(path_user = pair2, quiet = TRUE)) res <- with_dir(sub, data_admin_list_requests()) expect_equal(length(res), 1L) expect_equal(names(res), unclass(as.character(h))) with_dir(sub, data_admin_authorise(path_user = pair1, yes = TRUE, quiet = TRUE)) res <- with_dir(sub, data_admin_list_keys()) expect_equal(length(res), 2L) k1 <- with_dir(sub, data_key(path_user = pair1)) k2 <- with_dir(sub, data_key(path_user = pair2)) expect_identical(k1$key(), k2$key()) }) test_that("Custom messages", { path <- tempfile() dir.create(path, FALSE, TRUE) res <- data_admin_init(path, "pair1", quiet = TRUE) writeLines("my custom $HASH request message", file.path(data_path_template(path), "request")) writeLines("my custom $USERS authorise message", file.path(data_path_template(path), "authorise")) res1 <- testthat::evaluate_promise( data_request_access(path, "pair2")) res2 <- testthat::evaluate_promise( data_admin_authorise(path, res1$result, "pair1", TRUE)) expect_match( res1$messages, "my custom [[:xdigit:]:]+ request message", all = FALSE) expect_match( res2$messages, "my custom .+ authorise message", all = FALSE) }) test_that("fingerprint versioning", { k <- data_load_keypair_user("pair1")$pub expect_identical( data_key_fingerprint(k, numeric_version("1.0.3")), openssl::fingerprint(k, openssl::md5)) expect_identical( data_key_fingerprint(k, numeric_version("1.1.0")), openssl::fingerprint(k, openssl::sha256)) }) test_that("schema validation - old version produces warning the first time", { path <- unzip_reference("reference/1.0.0.zip") path_data <- file.path(path, "data") path_openssl_alice <- file.path(path, "openssl", "alice") expect_warning( data_version_read(path_data), "Your cyphr schema version is out of date (found 1.0.0, current is 1.1.0)", fixed = TRUE) expect_silent( data_version_read(path_data)) }) test_that("migrate", { path <- unzip_reference("reference/1.0.0.zip") path_data <- file.path(path, "data") path_openssl_alice <- file.path(path, "openssl", "alice") path_openssl_bob <- file.path(path, "openssl", "bob") suppressWarnings(data_version_read(path_data)) data_request_access(path_data, "pair3", quiet = TRUE) keys_old <- data_admin_list_keys(path_data) reqs_old <- data_admin_list_requests(path_data) res <- testthat::evaluate_promise(data_schema_migrate(path_data)) expect_match(res$messages, "Migrating key", all = FALSE) expect_match(res$messages, "Migrating request", all = FALSE) keys_new <- data_admin_list_keys(path_data) reqs_new <- data_admin_list_requests(path_data) map <- vapply(keys_old, function(k) { bin2str(data_key_fingerprint(k$pub, data_schema_version()), "") }, "") expect_setequal(names(keys_new), unname(map)) v <- c("user", "host", "date", "pub", "key") for (i in seq_along(map)) { expect_equal(keys_old[[i]][v], keys_new[[map[[i]]]][v]) } key1 <- data_key(path_data, path_openssl_alice) key2 <- data_key(path_data, path_openssl_bob) expect_identical(key1$key(), key2$key()) data_admin_list_requests(path_data) data_admin_authorise(path_data, path_user = path_openssl_alice, yes = TRUE, quiet = TRUE) key3 <- data_key(path_data, "pair3") expect_identical(key1$key(), key3$key()) res <- testthat::evaluate_promise(data_schema_migrate(path_data)) expect_match(res$messages, "Everything up to date!") }) test_that("schema validation - new version errors", { path <- tempfile() dir.create(path, FALSE) res <- data_admin_init(path, "pair1", quiet = TRUE) writeLines("9.9.9", data_path_version(path)) data_pkg_init() # clear cache expect_error( data_version_read(path), "Upgrade to cyphr version 9.9.9 (or newer)", fixed = TRUE) }) test_that("new data sources do not need migrating", { path <- tempfile() dir.create(path, FALSE) data_admin_init(path, "pair1", quiet = TRUE) res <- testthat::evaluate_promise(data_schema_migrate(path)) expect_match(res$messages, "Everything up to date!") }) test_that("cache data key", { path <- tempfile() dir.create(path, FALSE) data_admin_init(path, "pair1", quiet = TRUE) key1 <- data_key(path, "pair1") key2 <- data_key(path, "pair1") key3 <- data_key(path, "pair1", cache = FALSE) expect_identical(key1, key2) expect_false(identical(key1, key3)) })