test_that("raw round-trips for empty, tiny, and multi-chunk payloads", { p <- new_pair() for (msg in list("", "x", "hello world", strrep("A", 200000))) { ct <- age_encrypt_raw(charToRaw(msg), recipients = p$rec) expect_type(ct, "raw") pt <- age_decrypt_raw(ct, identities = p$id) expect_identical(rawToChar(pt), msg) } }) test_that("armored output is PEM and returns raw, round-trips", { p <- new_pair() ct <- age_encrypt_raw( charToRaw("armored secret"), recipients = p$rec, armor = TRUE ) expect_type(ct, "raw") expect_match(rawToChar(ct), "^-----BEGIN AGE ENCRYPTED FILE-----") expect_identical( rawToChar(age_decrypt_raw(ct, identities = p$id)), "armored secret" ) }) test_that("age_decrypt_raw accepts an armored character scalar", { p <- new_pair() ct <- age_encrypt_text("via string", recipients = p$rec) # split into lines and drop the trailing newline, as readLines() would lines <- strsplit(sub("\n$", "", ct), "\n", fixed = TRUE)[[1]] expect_identical( rawToChar(age_decrypt_raw(lines, identities = p$id)), "via string" ) }) test_that("text round-trips and preserves UTF-8", { p <- new_pair() msg <- "café ☃ \U0001F510" ct <- age_encrypt_text(msg, recipients = p$rec) expect_type(ct, "character") out <- age_decrypt_text(ct, identities = p$id) expect_identical(out, msg) expect_identical(Encoding(out), "UTF-8") }) test_that("age_decrypt_text rejects binary / non-UTF-8 payloads", { p <- new_pair() # embedded NUL ct1 <- age_encrypt_raw(as.raw(c(1, 0, 2)), recipients = p$rec) expect_error( age_decrypt_text(ct1, identities = p$id), class = "age_error_decrypt" ) # invalid UTF-8 byte sequence ct2 <- age_encrypt_raw(as.raw(c(0xff, 0xfe, 0x41)), recipients = p$rec) expect_error( age_decrypt_text(ct2, identities = p$id), class = "age_error_decrypt" ) }) test_that("file round-trip with default suffixes", { p <- new_pair() f <- withr::local_tempfile() writeBin(charToRaw("file contents here"), f) enc <- age_encrypt_file(f, recipients = p$rec) expect_identical(enc, paste0(f, ".age")) dec <- age_decrypt_file( enc, output = withr::local_tempfile(), identities = p$id ) expect_identical(readBin(dec, "raw", 100), charToRaw("file contents here")) }) test_that("multiple recipients can each decrypt", { a <- new_pair() b <- new_pair() ct <- age_encrypt_raw(charToRaw("shared"), recipients = c(a$rec, b$rec)) expect_identical(rawToChar(age_decrypt_raw(ct, identities = a$id)), "shared") expect_identical(rawToChar(age_decrypt_raw(ct, identities = b$id)), "shared") }) test_that("decrypt scans a multi-identity set for a match", { # Build a key file holding two identities; encrypt only to the second. kf1 <- withr::local_tempfile() kf2 <- withr::local_tempfile() age_keygen(kf1) age_keygen(kf2) combined <- withr::local_tempfile() writeLines(c(readLines(kf1), readLines(kf2)), combined) rec2 <- age_pubkey(age_identity(kf2)) ct <- age_encrypt_raw(charToRaw("for the second key"), recipients = rec2) ids <- age_identity(combined) # holds both keys as one C-side array expect_identical( rawToChar(age_decrypt_raw(ct, identities = ids)), "for the second key" ) }) # Regression: bwrite() in src/agec/io.c truncated its output when a write # landed exactly on the 8 KiB Obuf boundary. On encrypt this silently produced # a short, undecryptable ciphertext; on decrypt it silently returned a short # plaintext with no error at all. Both are data loss, so sweep the boundary. test_that("round-trips across the 8 KiB output-buffer boundary", { p <- new_pair() # encrypt side: header + payload nonce + final chunk crossing IOBUFSIZE for (n in c(7990L, 7992L, 8000L, 8176L, 8191L, 8192L, 8193L, 8208L)) { pt <- as.raw(rep.int(65L, n)) ct <- age_encrypt_raw(pt, recipients = p$rec) expect_identical( age_decrypt_raw(ct, identities = p$id), pt, info = paste("binary n =", n) ) } # decrypt side: a final STREAM chunk decrypting to exactly IOBUFSIZE bytes for (n in c(65536L + 8191L, 65536L + 8192L, 65536L + 8193L)) { pt <- as.raw(rep.int(66L, n)) ct <- age_encrypt_raw(pt, recipients = p$rec) expect_identical( age_decrypt_raw(ct, identities = p$id), pt, info = paste("multi-chunk n =", n) ) } }) test_that("armored round-trips across the output-buffer boundaries", { p <- new_pair() for (n in c(8192L, 12287L, 12288L, 12289L, 65536L + 8192L)) { pt <- as.raw(rep.int(67L, n)) ct <- age_encrypt_raw(pt, recipients = p$rec, armor = TRUE) expect_identical( age_decrypt_raw(ct, identities = p$id), pt, info = paste("armored n =", n) ) } })