From e6be95bc97bc33c27ea6150f8b49d423663052a9 Mon Sep 17 00:00:00 2001 From: Jonathan Keane Date: Mon, 18 Mar 2024 09:32:56 -0500 Subject: [PATCH 1/3] Update tests to use an RSA key --- tests/testthat/_snaps/http.md | 2 +- tests/testthat/fake-key | 1 + tests/testthat/test-http.R | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 tests/testthat/fake-key diff --git a/tests/testthat/_snaps/http.md b/tests/testthat/_snaps/http.md index 98543ef0..3b3530ff 100644 --- a/tests/testthat/_snaps/http.md +++ b/tests/testthat/_snaps/http.md @@ -22,7 +22,7 @@ Output List of 3 $ Date : chr "Thu, 09 Mar 2023 14:29:00 GMT" - $ X-Auth-Signature : chr "tqz4HGcSmuWKIGzIj42OEkwYZQzfJBdrUynlBQKSEEok2zMFZwsgEpEzU8PzpoeMEmcX5+Cr1IuDLLASz0ivAQ==" + $ X-Auth-Signature : chr "mk4e1sdK0Gy9Uex2nJMtkntdT/boQWRakSRB6iYw9hmP2zMHQjvynY+Kc5hqbGAK7tbzG52fC+5MQSOUapNKBF6GNnVe1cp2jFq4pmhEL2yhlkB"| __truncated__ $ X-Content-Checksum: chr "1B2M2Y8AsgTpgAmY7PhCfg==" # includes body in error if available diff --git a/tests/testthat/fake-key b/tests/testthat/fake-key new file mode 100644 index 00000000..d647ce09 --- /dev/null +++ b/tests/testthat/fake-key @@ -0,0 +1 @@ +MIIEpAIBAAKCAQEAv3okkAClwA7JahhrG7ELtnhIBBw9OgbVIWnlNZ77GO2urtdsaRtDgBmFoZxoUftSv96qGUEDZjdDAmVdM4bnkRLt2GqrTcF252xgqqe7bC7MdnG9k2bHJgTV5Di2odj5bAU3yJzZSq6Sh8LDLbC+8q7l8Ce+u3svCbozAKjvdI4+e4nX4ZK+CFOOxC/A4iB0zgGC3XSnv3eAC7JKBBUDN+0ifSVMGVLL89nqhFxT1eEK0vd0+/bAO0Hx+czmEyyjxA8VdFOwGFFSlmwYnlUZbtwyEH3bKl8GGMx1EfhwZcXtVaMAkuaeXOnKDS3W/kRJXEkmNhndOngYNMEqMZMk9wIDAQABAoIBAB5kIp2AnM5JCor+aTGx/ivuF3Afk1A8eWOeTTUfkLc6Mnmp05kzLJmOc3ldO+mXPGEb1F/Bw+pbZxVgRVTCWsrZ+EpbU+k5hcfhmc48ZTEclHNQRki5DW7pYZGhtsslhyEwCtSaoJqL7tIBhb0gyo32WKSvikRzntmqZiH6rlcPXegi5u7MMd9MfKhShAAnN4jPzHHPbijlp/jY1SqlFX1P/WZjcZo2fXt8JMIniNZkgYKvau35ghx5Blm1JBjKYYEcrLGiW8/m5/1s9rsHr77Xm5Ezs71rSoVBoAwcgggraidXoTwo3UY8pW+gubS28np4OUJ2Au7mSZkbBDlMDBECgYEA+j7B18CifCUZQxruBiUPXys2APgfjaNBirRrrkQw/LSaLo+a29XWbdfNVB1Fv1LYzac/I/JdUTdoD3Lv71Y8Elw3GR6Ib+HIEH0QjeO8ERBBG/yz/34cSLdh99tL4ear8HSSaBrxqCX0ebztV8rHSDwWoyIJxKOFO3GsGjX3ktUCgYEAw+FoF1G9AGLyupYIFVaoDc8cTEEVgkEhm8uESEh5Qtk6X7aWyxXa+nx7n/G8xquOtsRuTBTDBgbGxElyjVtfEAPriASA8LbWowH0OPjzbuJvtlJqfkXBrtjGKASaMYzMdNAWu+nBDBdPl/kTuM3H8GemPOvRWpKvg+65+GXrRpsCgYEAjLYLI3lDJFFsAgq7eqMOILJYfHUIsQjyir7mqafYb9BRvgqrxh9Yoo0s/LY1CN7Z39HCNEFM7aUdE0rK1aaEwsItjSdZCqhHadYZH9/FWUbthYIz6F8OImlTYh5ibdTaK6wwwu8boTQuYuG0B6CTK+/1vqceHP7hpMpHPrnHyz0CgYEAhvxPyjom4BxQH2sC2QmluBZw7s+vLdsKeR2P5GwlPH8Mbica1YsTI6kjXH6vU82oBKVrSPzJxN5onZ3r1iQQZ6374vkPjlLBqQXQsm5E+7YJvAAhqTETHxX9wFgjll/sCdfYwth8k4OA8z7Pa3xL+4zCD5uG4z7Quz+JYveBYl8CgYAU4ebT1BLxtHIWb6kU9oNL71oCRhedhQlwYo93ZzZdXjUUGGwyPoTW2WHBwihymjqA1mqmIcoy5Ok89C4S3l1gJTj+rRNlGxIItdMMmhCPRMn14NZoIh5UoPqGTAsdOU1BOj683XUuqlDiXzez92t4RBYo7I4P/hQzK/ogFc468g== diff --git a/tests/testthat/test-http.R b/tests/testthat/test-http.R index 39b42485..dfff8282 100644 --- a/tests/testthat/test-http.R +++ b/tests/testthat/test-http.R @@ -22,8 +22,8 @@ test_that("authHeaders() picks correct method based on supplied fields", { ) # Dummy key created with - # openssl::base64_encode(openssl::ed25519_keygen()) - key <- "MC4CAQAwBQYDK2VwBCIEIDztfEgkp5CX7Jz0NCyrToaRW1L2tfmrWxNDgYyjO9bQ" + # openssl::base64_encode(openssl::rsa_keygen(2048L)) + key <- readLines(test_path("fake-key")) expect_snapshot({ str(authHeaders(list(secret = "123"), url, "GET")) From 17f2eff95fbece477dce91b6c341499063077b08 Mon Sep 17 00:00:00 2001 From: Jonathan Keane Date: Mon, 18 Mar 2024 16:01:07 -0500 Subject: [PATCH 2/3] openssl::signature_create() -> digest() + PKI.sign() --- DESCRIPTION | 1 + R/http.R | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 2135cd5b..a76a658b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -26,6 +26,7 @@ Imports: jsonlite, lifecycle, openssl (>= 2.0.0), + PKI, packrat (>= 0.6), renv (>= 1.0.0), rlang (>= 1.0.0), diff --git a/R/http.R b/R/http.R index 467fa744..e75b59aa 100644 --- a/R/http.R +++ b/R/http.R @@ -501,8 +501,14 @@ signatureHeaders <- function(authInfo, method, path, file = NULL) { der = TRUE ) - # OpenSSL defaults to sha1 hash function (which is what we need) - rawsig <- openssl::signature_create(charToRaw(canonicalRequest), key = private_key) + # convert key into PKI format for signing, note this only accepts RSA, but + # that's what rsconnect generates already + pki_key <- PKI::PKI.load.key(strsplit(openssl::write_pem(private_key), "\n")[[1]], format = "PEM") + + # use sha1 digest and then sign. digest and PKI avoid using system openssl which + # can be problematic in strict FIPS environments + digested <- digest::digest(charToRaw(canonicalRequest), "sha1", serialize = FALSE, raw = TRUE) + rawsig <- PKI::PKI.sign(key = pki_key, digest = digested) signature <- openssl::base64_encode(rawsig) } else { stop("can't sign request: no shared secret or private key") From 06b40f008308f67a878401a6f882c81a8a0dc491 Mon Sep 17 00:00:00 2001 From: Jonathan Keane Date: Fri, 22 Mar 2024 08:54:49 -0500 Subject: [PATCH 3/3] =?UTF-8?q?Move=20key=20inline,=20test=20equivalence,?= =?UTF-8?q?=20=F0=9F=97=9E=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NEWS.md | 3 +++ R/http.R | 24 ++++++++++++------- tests/testthat/fake-key | 1 - tests/testthat/test-http.R | 48 +++++++++++++++++++++++++++++++++++++- 4 files changed, 65 insertions(+), 11 deletions(-) delete mode 100644 tests/testthat/fake-key diff --git a/NEWS.md b/NEWS.md index 8d0b2cd3..e63be671 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # rsconnect (development version) +* Use internally computed SHA1 sums and PKI signing when SHA1 is disabled + in FIPS mode (#768, #1054) + # rsconnect 1.2.1 * Restore the `LC_TIME` locale after computing an RFC-2616 date. (#1035) diff --git a/R/http.R b/R/http.R index e75b59aa..79f07ae7 100644 --- a/R/http.R +++ b/R/http.R @@ -501,15 +501,7 @@ signatureHeaders <- function(authInfo, method, path, file = NULL) { der = TRUE ) - # convert key into PKI format for signing, note this only accepts RSA, but - # that's what rsconnect generates already - pki_key <- PKI::PKI.load.key(strsplit(openssl::write_pem(private_key), "\n")[[1]], format = "PEM") - - # use sha1 digest and then sign. digest and PKI avoid using system openssl which - # can be problematic in strict FIPS environments - digested <- digest::digest(charToRaw(canonicalRequest), "sha1", serialize = FALSE, raw = TRUE) - rawsig <- PKI::PKI.sign(key = pki_key, digest = digested) - signature <- openssl::base64_encode(rawsig) + signature <- signRequestPrivateKey(private_key, canonicalRequest) } else { stop("can't sign request: no shared secret or private key") } @@ -522,6 +514,20 @@ signatureHeaders <- function(authInfo, method, path, file = NULL) { headers } +signRequestPrivateKey <- function(private_key, canonicalRequest) { + # convert key into PKI format for signing, note this only accepts RSA, but + # that's what rsconnect generates already + pem <- openssl::write_pem(private_key) + pem_lines <- readLines(textConnection(pem)) + pki_key <- PKI::PKI.load.key(pem_lines, format = "PEM") + + # use sha1 digest and then sign. digest and PKI avoid using system openssl which + # can be problematic in strict FIPS environments + digested <- digest::digest(charToRaw(canonicalRequest), "sha1", serialize = FALSE, raw = TRUE) + rawsig <- PKI::PKI.sign(key = pki_key, digest = digested) + openssl::base64_encode(rawsig) +} + rfc2616Date <- function(time = Sys.time()) { # set locale to POSIX/C to ensure ASCII date old <- Sys.getlocale("LC_TIME") diff --git a/tests/testthat/fake-key b/tests/testthat/fake-key deleted file mode 100644 index d647ce09..00000000 --- a/tests/testthat/fake-key +++ /dev/null @@ -1 +0,0 @@ -MIIEpAIBAAKCAQEAv3okkAClwA7JahhrG7ELtnhIBBw9OgbVIWnlNZ77GO2urtdsaRtDgBmFoZxoUftSv96qGUEDZjdDAmVdM4bnkRLt2GqrTcF252xgqqe7bC7MdnG9k2bHJgTV5Di2odj5bAU3yJzZSq6Sh8LDLbC+8q7l8Ce+u3svCbozAKjvdI4+e4nX4ZK+CFOOxC/A4iB0zgGC3XSnv3eAC7JKBBUDN+0ifSVMGVLL89nqhFxT1eEK0vd0+/bAO0Hx+czmEyyjxA8VdFOwGFFSlmwYnlUZbtwyEH3bKl8GGMx1EfhwZcXtVaMAkuaeXOnKDS3W/kRJXEkmNhndOngYNMEqMZMk9wIDAQABAoIBAB5kIp2AnM5JCor+aTGx/ivuF3Afk1A8eWOeTTUfkLc6Mnmp05kzLJmOc3ldO+mXPGEb1F/Bw+pbZxVgRVTCWsrZ+EpbU+k5hcfhmc48ZTEclHNQRki5DW7pYZGhtsslhyEwCtSaoJqL7tIBhb0gyo32WKSvikRzntmqZiH6rlcPXegi5u7MMd9MfKhShAAnN4jPzHHPbijlp/jY1SqlFX1P/WZjcZo2fXt8JMIniNZkgYKvau35ghx5Blm1JBjKYYEcrLGiW8/m5/1s9rsHr77Xm5Ezs71rSoVBoAwcgggraidXoTwo3UY8pW+gubS28np4OUJ2Au7mSZkbBDlMDBECgYEA+j7B18CifCUZQxruBiUPXys2APgfjaNBirRrrkQw/LSaLo+a29XWbdfNVB1Fv1LYzac/I/JdUTdoD3Lv71Y8Elw3GR6Ib+HIEH0QjeO8ERBBG/yz/34cSLdh99tL4ear8HSSaBrxqCX0ebztV8rHSDwWoyIJxKOFO3GsGjX3ktUCgYEAw+FoF1G9AGLyupYIFVaoDc8cTEEVgkEhm8uESEh5Qtk6X7aWyxXa+nx7n/G8xquOtsRuTBTDBgbGxElyjVtfEAPriASA8LbWowH0OPjzbuJvtlJqfkXBrtjGKASaMYzMdNAWu+nBDBdPl/kTuM3H8GemPOvRWpKvg+65+GXrRpsCgYEAjLYLI3lDJFFsAgq7eqMOILJYfHUIsQjyir7mqafYb9BRvgqrxh9Yoo0s/LY1CN7Z39HCNEFM7aUdE0rK1aaEwsItjSdZCqhHadYZH9/FWUbthYIz6F8OImlTYh5ibdTaK6wwwu8boTQuYuG0B6CTK+/1vqceHP7hpMpHPrnHyz0CgYEAhvxPyjom4BxQH2sC2QmluBZw7s+vLdsKeR2P5GwlPH8Mbica1YsTI6kjXH6vU82oBKVrSPzJxN5onZ3r1iQQZ6374vkPjlLBqQXQsm5E+7YJvAAhqTETHxX9wFgjll/sCdfYwth8k4OA8z7Pa3xL+4zCD5uG4z7Quz+JYveBYl8CgYAU4ebT1BLxtHIWb6kU9oNL71oCRhedhQlwYo93ZzZdXjUUGGwyPoTW2WHBwihymjqA1mqmIcoy5Ok89C4S3l1gJTj+rRNlGxIItdMMmhCPRMn14NZoIh5UoPqGTAsdOU1BOj683XUuqlDiXzez92t4RBYo7I4P/hQzK/ogFc468g== diff --git a/tests/testthat/test-http.R b/tests/testthat/test-http.R index dfff8282..afbe3d94 100644 --- a/tests/testthat/test-http.R +++ b/tests/testthat/test-http.R @@ -23,12 +23,58 @@ test_that("authHeaders() picks correct method based on supplied fields", { # Dummy key created with # openssl::base64_encode(openssl::rsa_keygen(2048L)) - key <- readLines(test_path("fake-key")) + key_string <- "-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/eiSQAKXADslq +GGsbsQu2eEgEHD06BtUhaeU1nvsY7a6u12xpG0OAGYWhnGhR+1K/3qoZQQNmN0MC +ZV0zhueREu3YaqtNwXbnbGCqp7tsLsx2cb2TZscmBNXkOLah2PlsBTfInNlKrpKH +wsMtsL7yruXwJ767ey8JujMAqO90jj57idfhkr4IU47EL8DiIHTOAYLddKe/d4AL +skoEFQM37SJ9JUwZUsvz2eqEXFPV4QrS93T79sA7QfH5zOYTLKPEDxV0U7AYUVKW +bBieVRlu3DIQfdsqXwYYzHUR+HBlxe1VowCS5p5c6coNLdb+RElcSSY2Gd06eBg0 +wSoxkyT3AgMBAAECggEAHmQinYCczkkKiv5pMbH+K+4XcB+TUDx5Y55NNR+Qtzoy +eanTmTMsmY5zeV076Zc8YRvUX8HD6ltnFWBFVMJaytn4SltT6TmFx+GZzjxlMRyU +c1BGSLkNbulhkaG2yyWHITAK1Jqgmovu0gGFvSDKjfZYpK+KRHOe2apmIfquVw9d +6CLm7swx30x8qFKEACc3iM/Mcc9uKOWn+NjVKqUVfU/9ZmNxmjZ9e3wkwieI1mSB +gq9q7fmCHHkGWbUkGMphgRyssaJbz+bn/Wz2uwevvtebkTOzvWtKhUGgDByCCCtq +J1ehPCjdRjylb6C5tLbyeng5QnYC7uZJmRsEOUwMEQKBgQD6PsHXwKJ8JRlDGu4G +JQ9fKzYA+B+No0GKtGuuRDD8tJouj5rb1dZt181UHUW/UtjNpz8j8l1RN2gPcu/v +VjwSXDcZHohv4cgQfRCN47wREEEb/LP/fhxIt2H320vh5qvwdJJoGvGoJfR5vO1X +ysdIPBajIgnEo4U7cawaNfeS1QKBgQDD4WgXUb0AYvK6lggVVqgNzxxMQRWCQSGb +y4RISHlC2TpftpbLFdr6fHuf8bzGq462xG5MFMMGBsbESXKNW18QA+uIBIDwttaj +AfQ4+PNu4m+2Ump+RcGu2MYoBJoxjMx00Ba76cEMF0+X+RO4zcfwZ6Y869Fakq+D +7rn4ZetGmwKBgQCMtgsjeUMkUWwCCrt6ow4gslh8dQixCPKKvuapp9hv0FG+CqvG +H1iijSz8tjUI3tnf0cI0QUztpR0TSsrVpoTCwi2NJ1kKqEdp1hkf38VZRu2FgjPo +Xw4iaVNiHmJt1NorrDDC7xuhNC5i4bQHoJMr7/W+px4c/uGkykc+ucfLPQKBgQCG +/E/KOibgHFAfawLZCaW4FnDuz68t2wp5HY/kbCU8fwxuJxrVixMjqSNcfq9TzagE +pWtI/MnE3midnevWJBBnrfvi+Q+OUsGpBdCybkT7tgm8ACGpMRMfFf3AWCOWX+wJ +19jC2HyTg4DzPs9rfEv7jMIPm4bjPtC7P4li94FiXwKBgBTh5tPUEvG0chZvqRT2 +g0vvWgJGF52FCXBij3dnNl1eNRQYbDI+hNbZYcHCKHKaOoDWaqYhyjLk6Tz0LhLe +XWAlOP6tE2UbEgi10wyaEI9EyfXg1mgiHlSg+oZMCx05TUE6PrzddS6qUOJfN7P3 +a3hEFijsjg/+FDMr+iAVzjry +-----END PRIVATE KEY-----" + key <- openssl::base64_encode(openssl::read_key(key_string)) expect_snapshot({ str(authHeaders(list(secret = "123"), url, "GET")) str(authHeaders(list(private_key = key), url, "GET")) }) + + # and that signRequestPrivateKey() is the same as openssl equivalent + # authHeaders does this conversion internally + private_key <- openssl::read_key( + openssl::base64_decode(key), + der = TRUE + ) + + # an alternative implementation with openssl + signRequestPrivateKeyOpenSSL <- function(private_key, canonicalRequest) { + rawsig <- openssl::signature_create(charToRaw(canonicalRequest), key = private_key) + openssl::base64_encode(rawsig) + } + + expect_identical( + signRequestPrivateKey(private_key, url), + signRequestPrivateKeyOpenSSL(private_key, url) + ) }) test_that("can add user specific headers", {