diff --git a/.github/workflows/R-CMD-check-dev.yaml b/.github/workflows/R-CMD-check-dev.yaml index a03ff14..abd9777 100644 --- a/.github/workflows/R-CMD-check-dev.yaml +++ b/.github/workflows/R-CMD-check-dev.yaml @@ -18,7 +18,9 @@ jobs: fail-fast: false matrix: config: + # - {os: windows-latest, r: 'release'} - {os: ubuntu-latest, r: 'release'} + # - {os: ubuntu-latest, r: 'oldrel-1'} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} diff --git a/DESCRIPTION b/DESCRIPTION index fd17a88..340f94a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: pqcrypto Title: Post-Quantum Cryptography -Version: 0.2.0 +Version: 0.2.1 Authors@R: person("Bruno", "Gonçalves", , "bpvgoncalves@users.noreply.github.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-0797-7717")) @@ -13,6 +13,7 @@ Imports: openssl, PKI Suggests: + httptest2, httr2, knitr, rmarkdown, diff --git a/NEWS.md b/NEWS.md index 98912bf..8315d3c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,12 @@ +# pqcrypto 0.2.1 + +#### Fix + - TSA secure time stamp defaults to use system time on http error or disconnection + +#### Internals + - Fix some problems preventing R CMD Check to work in some R/OS versions + - Add additional tests to the suite + # pqcrypto 0.2.0 #### New Features diff --git a/R/as_der.R b/R/as_der.R index 89407c8..617cc15 100644 --- a/R/as_der.R +++ b/R/as_der.R @@ -29,7 +29,7 @@ as.der.pqcrypto_timestamp <- function (ts) { # dates in 2050 or later MUST be encoded as GeneralizedTime. # -- RFC 5280 - dt <- as.POSIXlt(attr(ts, "unix_ts"), tz="UTC") + dt <- as.POSIXlt(attr(ts, "unix_ts"), tz="UTC", origin = "1970-01-01") if (dt$year <= 49 || dt$year >= 150) { # GeneralizedTime strdate <- strftime(dt, "%Y%m%d%H%M%SZ", tz="UTC") diff --git a/R/pqcrypto-package.R b/R/pqcrypto-package.R index ce341db..9fedb24 100644 --- a/R/pqcrypto-package.R +++ b/R/pqcrypto-package.R @@ -19,6 +19,8 @@ ## usethis namespace: end NULL +# Required for testthat mocking to work +requireNamespace <- NULL #' @export print.pqcrypto_keypair <- function(x, ...) { diff --git a/R/tsp.R b/R/tsp.R index 93dbb9f..f077e8f 100644 --- a/R/tsp.R +++ b/R/tsp.R @@ -7,31 +7,38 @@ get_timestamp_secure <- function(tsq) { req <- httr2::req_method(req, "POST") req <- httr2::req_body_raw(req, dertsq, "application/timestamp-query") - try(httr2::req_perform(req), silent = TRUE) - resp <- httr2::last_response() + try_resp <- try(httr2::req_perform(req), silent = TRUE) - if(resp$status_code == 200L) { + if (inherits(try_resp, "httr2_response")) { + resp <- try_resp + } else { + resp <- NULL + } + if (!is.null(resp) && resp$status_code == 200L) { tsr <- PKI::ASN1.decode(resp$body) if (tsr[[1]][[1]] == 0) { # PKIStatus ::= INTEGER { granted (0), grantedWithMods (1), rejection (2), waiting (3), # revocationWarning (4), revocationNotification (5) } ts <- PKI::ASN1.decode(PKI::ASN1.decode(PKI::ASN1.decode(tsr[[2]][[2]])[[3]][[2]]))[[5]] - ts <- as.POSIXct(rawToChar(ts), format = "%Y%m%d%H%M%SZ", tz="UTC") + ts <- as.POSIXct(rawToChar(ts), format = "%Y%m%d%H%M%SZ", tz = "UTC") - out <- list(ts = structure(strftime(ts, "%Y-%m-%dT%H:%M:%SZ", tz="UTC"), + out <- list(ts = structure(strftime(ts, "%Y-%m-%dT%H:%M:%SZ", tz = "UTC"), unix_ts = as.integer(ts), class = "pqcrypto_timestamp"), tsr = structure(resp$body, class = "pqcrypto_tsp_tsr")) } else { + pq_msg(c(i="Time stamp not granted by the TSA. Using system time.")) out <- list(ts = get_timestamp(), tsr = NULL) } } else { + pq_msg(c(i="Invalid or no response from the TSA. Using system time.")) out <- list(ts = get_timestamp(), tsr = NULL) } } else { + pq_msg(c(i="Package 'httr2' is not available. Using system time.")) out <- list(ts = get_timestamp(), tsr = NULL) } diff --git a/R/utils.R b/R/utils.R index eed93fb..0fc1145 100644 --- a/R/utils.R +++ b/R/utils.R @@ -33,9 +33,9 @@ key_from_pass <- function(x) { get_timestamp <- function() { - uts <- as.integer(Sys.time()) - ts <- strftime(uts, "%Y-%m-%dT%H:%M:%SZ", tz="UTC") - attr(ts, "unix_ts") <- uts + now <- Sys.time() + ts <- strftime(now, "%Y-%m-%dT%H:%M:%SZ", tz="UTC") + attr(ts, "unix_ts") <- as.integer(now) class(ts) <- "pqcrypto_timestamp" invisible(ts) diff --git a/tests/testthat.R b/tests/testthat.R index 3a55bcd..ec5efdc 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -7,6 +7,7 @@ # * https://testthat.r-lib.org/articles/special-files.html library(testthat) +library(httptest2) library(pqcrypto) test_check("pqcrypto") diff --git a/tests/testthat/test-envelope_write.R b/tests/testthat/test-envelope_write.R index cb785d8..5fa3cd9 100644 --- a/tests/testthat/test-envelope_write.R +++ b/tests/testthat/test-envelope_write.R @@ -18,11 +18,11 @@ test_that("Envelope writing works", { "'envelope' parameter does not have the expected class") if (Sys.info()[1] == "Linux") { - dest <- "/home/file.env" # Shouldn't be able to write into root's home dir + dest <- "/fake_path/file.env" } else if ((Sys.info()[1] == "Windows")) { - dest <- "c:/Windows/system32/file.env" # Shouldn't be able to write into system dir + dest <- "z:/fake_path/file.env" } else { skip("Unknown OS") } - expect_error(write_envelope(env, dest), "Permission denied") + expect_error(write_envelope(env, dest), "cannot open file") }) diff --git a/tests/testthat/test-sign_dillitium.R b/tests/testthat/test-sign_dillitium.R index d8a86fa..3733e7d 100644 --- a/tests/testthat/test-sign_dillitium.R +++ b/tests/testthat/test-sign_dillitium.R @@ -22,6 +22,13 @@ test_that("Dilithium2 digital signature", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.2.1") expect_equal(length(sig$signer_infos$signature), 2420) + + httptest2::without_internet({ + sig <- sign_dilithium(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.2.1") + expect_equal(length(sig$signer_infos$signature), 2420) + }) }) test_that("Dilithium3 digital signature", { @@ -48,6 +55,13 @@ test_that("Dilithium3 digital signature", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.2.2") expect_equal(length(sig$signer_infos$signature), 3309) + + httptest2::without_internet({ + sig <- sign_dilithium(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.2.2") + expect_equal(length(sig$signer_infos$signature), 3309) + }) }) test_that("Dilithium5 digital signature", { @@ -74,6 +88,13 @@ test_that("Dilithium5 digital signature", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.2.3") expect_equal(length(sig$signer_infos$signature), 4627) + + httptest2::without_internet({ + sig <- sign_dilithium(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.2.3") + expect_equal(length(sig$signer_infos$signature), 4627) + }) }) diff --git a/tests/testthat/test-sign_sphincs.R b/tests/testthat/test-sign_sphincs.R index 0ba5cde..e728f2b 100644 --- a/tests/testthat/test-sign_sphincs.R +++ b/tests/testthat/test-sign_sphincs.R @@ -21,6 +21,13 @@ test_that("Sphincs+ signature (shake, 128, small)", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.2") expect_equal(length(sig$signer_infos$signature), 7856) + + httptest2::without_internet({ + sig <- sign_sphincs(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.2") + expect_equal(length(sig$signer_infos$signature), 7856) + }) }) test_that("Sphincs+ signature (shake, 128, fast)", { @@ -46,6 +53,13 @@ test_that("Sphincs+ signature (shake, 128, fast)", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.4") expect_equal(length(sig$signer_infos$signature), 17088) + + httptest2::without_internet({ + sig <- sign_sphincs(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.4") + expect_equal(length(sig$signer_infos$signature), 17088) + }) }) test_that("Sphincs+ signature (shake, 192, small)", { @@ -71,6 +85,13 @@ test_that("Sphincs+ signature (shake, 192, small)", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.6") expect_equal(length(sig$signer_infos$signature), 16224) + + httptest2::without_internet({ + sig <- sign_sphincs(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.6") + expect_equal(length(sig$signer_infos$signature), 16224) + }) }) test_that("Sphincs+ signature (shake, 192, fast)", { @@ -96,6 +117,13 @@ test_that("Sphincs+ signature (shake, 192, fast)", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.8") expect_equal(length(sig$signer_infos$signature), 35664) + + httptest2::without_internet({ + sig <- sign_sphincs(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.8") + expect_equal(length(sig$signer_infos$signature), 35664) + }) }) test_that("Sphincs+ signature (shake, 256, small)", { @@ -121,6 +149,13 @@ test_that("Sphincs+ signature (shake, 256, small)", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.10") expect_equal(length(sig$signer_infos$signature), 29792) + + httptest2::without_internet({ + sig <- sign_sphincs(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.10") + expect_equal(length(sig$signer_infos$signature), 29792) + }) }) test_that("Sphincs+ signature (shake, 256, fast)", { @@ -146,6 +181,13 @@ test_that("Sphincs+ signature (shake, 256, fast)", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.12") expect_equal(length(sig$signer_infos$signature), 49856) + + httptest2::without_internet({ + sig <- sign_sphincs(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.12") + expect_equal(length(sig$signer_infos$signature), 49856) + }) }) test_that("Sphincs+ signature (sha2, 128, small)", { @@ -171,6 +213,13 @@ test_that("Sphincs+ signature (sha2, 128, small)", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.1") expect_equal(length(sig$signer_infos$signature), 7856) + + httptest2::without_internet({ + sig <- sign_sphincs(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.1") + expect_equal(length(sig$signer_infos$signature), 7856) + }) }) test_that("Sphincs+ signature (sha2, 128, fast)", { @@ -196,6 +245,13 @@ test_that("Sphincs+ signature (sha2, 128, fast)", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.3") expect_equal(length(sig$signer_infos$signature), 17088) + + httptest2::without_internet({ + sig <- sign_sphincs(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.3") + expect_equal(length(sig$signer_infos$signature), 17088) + }) }) test_that("Sphincs+ signature (sha2, 192, small)", { @@ -221,6 +277,13 @@ test_that("Sphincs+ signature (sha2, 192, small)", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.5") expect_equal(length(sig$signer_infos$signature), 16224) + + httptest2::without_internet({ + sig <- sign_sphincs(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.5") + expect_equal(length(sig$signer_infos$signature), 16224) + }) }) test_that("Sphincs+ signature (sha2, 192, fast)", { @@ -246,6 +309,13 @@ test_that("Sphincs+ signature (sha2, 192, fast)", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.7") expect_equal(length(sig$signer_infos$signature), 35664) + + httptest2::without_internet({ + sig <- sign_sphincs(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.7") + expect_equal(length(sig$signer_infos$signature), 35664) + }) }) test_that("Sphincs+ signature (sha2, 256, small)", { @@ -271,6 +341,13 @@ test_that("Sphincs+ signature (sha2, 256, small)", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.9") expect_equal(length(sig$signer_infos$signature), 29792) + + httptest2::without_internet({ + sig <- sign_sphincs(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.9") + expect_equal(length(sig$signer_infos$signature), 29792) + }) }) test_that("Sphincs+ signature (sha2, 256, fast)", { @@ -296,6 +373,13 @@ test_that("Sphincs+ signature (sha2, 256, fast)", { expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.11") expect_equal(length(sig$signer_infos$signature), 49856) + + httptest2::without_internet({ + sig <- sign_sphincs(key$private, "Hello world!!") + expect_true(inherits(sig, "pqcrypto_cms_id_signed_data")) + expect_equal(sig$signer_infos$signature_algorithm, "1.3.6.1.4.1.54392.5.1859.1.3.11") + expect_equal(length(sig$signer_infos$signature), 49856) + }) }) test_that("Sphincs+ digital signature fails on wrong parameters", { diff --git a/tests/testthat/test-signature_write.R b/tests/testthat/test-signature_write.R index 4ae667f..dc6cca2 100644 --- a/tests/testthat/test-signature_write.R +++ b/tests/testthat/test-signature_write.R @@ -18,13 +18,13 @@ test_that("Signature Writing works: Dilithium", { "'signature' parameter does not have the expected class") if (Sys.info()[1] == "Linux") { - dest <- "/home/file.sig" # Shouldn't be able to write into root's home dir + dest <- "/fake_path/file.env" } else if ((Sys.info()[1] == "Windows")) { - dest <- "c:/Windows/system32/file.sig" # Shouldn't be able to write into system dir + dest <- "z:/fake_path/file.env" } else { skip("Unknown OS") } - expect_error(write_signature(signature, dest), "Permission denied") + expect_error(write_signature(signature, dest), "cannot open file") }) @@ -48,11 +48,11 @@ test_that("Signature Writing works: Sphincs+", { "'signature' parameter does not have the expected class") if (Sys.info()[1] == "Linux") { - dest <- "/home/file.sig" # Shouldn't be able to write into root's home dir + dest <- "/fake_path/file.env" } else if ((Sys.info()[1] == "Windows")) { - dest <- "c:/Windows/system32/file.sig" # Shouldn't be able to write into system dir + dest <- "z:/fake_path/file.env" } else { skip("Unknown OS") } - expect_error(write_signature(signature, dest), "Permission denied") + expect_error(write_signature(signature, dest), "cannot open file") }) diff --git a/tests/testthat/test-tsp.R b/tests/testthat/test-tsp.R new file mode 100644 index 0000000..e21c819 --- /dev/null +++ b/tests/testthat/test-tsp.R @@ -0,0 +1,43 @@ + +test_that("secure time stamp testing", { + + content <- as.cms_data("message") + tsq <- as.tsp_tsq(c(content)) + + # No internet connection (use system time) + httptest2::without_internet({ + + expect_message({ts = get_timestamp_secure(tsq)}, + "Invalid or no response") + + expect_true(inherits(ts, "list")) + expect_length(ts, 2) + expect_true(inherits(ts[[1]], "pqcrypto_timestamp")) + expect_null(ts[[2]]) + }) + + # Test output when httr2 package is not available. + rN <- function(...) FALSE + with_mocked_bindings({ + expect_message({ts = get_timestamp_secure(tsq)}, + "Package 'httr2' is not available") + + expect_true(inherits(ts, "list")) + expect_length(ts, 2) + expect_true(inherits(ts[[1]], "pqcrypto_timestamp")) + expect_null(ts[[2]]) + }, + requireNamespace = rN + ) + + # Force unsupported algo (http 200, PKIStatus != 0) + tsq$message_imprint$algo <- "1.2.840.113549.1.9.4" + + expect_message({ts = get_timestamp_secure(tsq)}, + "Time stamp not granted") + + expect_true(inherits(ts, "list")) + expect_length(ts, 2) + expect_true(inherits(ts[[1]], "pqcrypto_timestamp")) + expect_null(ts[[2]]) +}) diff --git a/tests/testthat/test-verify_sphincs.R b/tests/testthat/test-verify_sphincs.R index 76cb5f7..fd9a07f 100644 --- a/tests/testthat/test-verify_sphincs.R +++ b/tests/testthat/test-verify_sphincs.R @@ -302,3 +302,31 @@ test_that("Sphincs+ digital signatures verification fails with bad parameters", expect_error(verify_sphincs(important_message, signature, key$public)) # mismatching key }) + +test_that("Sphincs+ digital signatures verification fails with bad parameters", { + + key <- keygen_sphincs("sha2") + important_message <- "Hello world!!" + signature <- sign_sphincs(key$private, important_message) + + expect_error(verify_sphincs(important_message, "not_a_signature", key$public)) # bad signature + expect_error(verify_sphincs(important_message, signature, key$private)) # bad key type + + small_signature <- signature + small_signature$signer_infos$signature <- small_signature$signer_infos$signature[1:100] + class(small_signature) <- "pqcrypto_cms_id_signed_data" + expect_error(verify_sphincs(important_message, small_signature, key$public)) # c++ error + + key <- keygen_kyber() + expect_error(verify_sphincs(important_message, signature, key$public)) # bad key algorithm + + key <- keygen_dilithium() + expect_error(verify_sphincs(important_message, signature, key$public)) # bad key algorithm + + key <- keygen_kyber() + expect_error(verify_sphincs(important_message, signature, key$public)) # bad key algorithm + + key <- keygen_sphincs() + expect_error(verify_sphincs(important_message, signature, key$public)) # mismatching key + +})