-
Notifications
You must be signed in to change notification settings - Fork 84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sha1 token signing workaround #1054
Conversation
R/http.R
Outdated
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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure there's a better way to do this, but I wanted to get feedback on this quicker
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is equivalent:
pem <- openssl::write_pem(private_key)
pem_lines <- readLines(textConnection(pem))
pki_key <- PKI::PKI.load.key(pem_lines, format = "PEM")
tests/testthat/test-http.R
Outdated
# openssl::base64_encode(openssl::rsa_keygen(2048L)) | ||
key <- readLines(test_path("fake-key")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PKI doesn't support non-RSA keys. If I understand correctly this is totally fine because we only generate RSA keys.
Was the use of ed25519 just because it was shorter?
I also committed these changes separately to be able to pull that and see if tests past there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing so, yes (shorter).
I would also be fine if the fake-key
were a constant in-code rather than a file living alongside the tests. You could store it in PEM format, which has the start/end markers and (generally) shorter lines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I pulled the PEM format into the test. It's not the prettiest, but the sidecar file also isn't either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like we should have a test proving that this implementation mirrors the old implementation. Right now, we're using the textual snapshot as a way of convincing ourselves, but it would be nice to have something that's enforced by code, as well.
It might mean refactoring signatureHeaders
so we can pass the canonicalRequest
into some signRequest
helper, but that would let us test equivalence beyond the signature check. We could confirm that the "new" implementation returns the same signature as the old approach, which used openssl.
signRequestPrivateKey_old <- function(private_key, canonicalRequest) {
rawsig <- openssl::signature_create(charToRaw(canonicalRequest), key = private_key)
openssl::base64_encode(rawsig)
}
signRequestPrivateKey_new <- function(private_key, canonicalRequest) {
pem <- openssl::write_pem(private_key)
key <- readLines(textConnection(pem))
pki_key <- PKI::PKI.load.key(key, format = "PEM")
digested <- digest::digest(charToRaw(canonicalRequest), "sha1", serialize = FALSE, raw = TRUE)
rawsig <- PKI::PKI.sign(key = pki_key, digest = digested)
openssl::base64_encode(rawsig)
}
R/http.R
Outdated
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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is equivalent:
pem <- openssl::write_pem(private_key)
pem_lines <- readLines(textConnection(pem))
pki_key <- PKI::PKI.load.key(pem_lines, format = "PEM")
tests/testthat/test-http.R
Outdated
# openssl::base64_encode(openssl::rsa_keygen(2048L)) | ||
key <- readLines(test_path("fake-key")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing so, yes (shorter).
I would also be fine if the fake-key
were a constant in-code rather than a file living alongside the tests. You could store it in PEM format, which has the start/end markers and (generally) shorter lines.
Forgot to mention: This needs NEWS. |
Thanks for the detailed review. I'm broadly onboard with the suggestions and will get to doing them shortly. But if this ends up blocking anything, (as always) I don't mind if you pushed to this branch to unblock it. |
This is ready for review again |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for that additional test.
Of course. Oh and I should have mentioned this FTR in the previous comment: I wanted to split that test out into it's own |
No worries. You could create a helper function adjacent to the generateTokenFixed <- function() {
return list(
token = "TDECAFBAD",
public_key = "...",
private_key = "...",
)
}
test_that("thing one", {
token <- generateTokenFixed()
# check for stable signature (snapshot)
})
test_that("thing two", {
token <- generateTokenFixed()
# check for equivalent signature (openssl)
}) I don't think this adjustment is necessary, though. If we were to continue to expand the tests in this area, it's a natural next step. |
Unfortunately (re)-introducing the PKI dependency in this PR makes it impossible to install rsconnect on MacOS when R is installed from homebrew rather than CRAN: #1073 |
Seeing the use of
digest
for md5 hashes made me realize that we could do the same for the sha1 hashs (and PKI for signing).I tested this publishing to connect using an existing token from RStudio and it worked just fine.
I also ran the tests in docker running rockylinux9 with FIPS mode enabled (using the docker file described in #928 and running
R CMD build . && R CMD check .
) There was one test that fails, though this looks like an encoding issue unless I'm missing something.We should still move away from sha1 (possibly by moving to default to API keys, which already work and avoid this issue), but this PR buys us some time to do that work (both here and on the connect side).