R/openssl_keygen.R

Defines functions ssh_keygen

Documented in ssh_keygen

##' Create openssl key pairs in the manner of `ssh-keygen`(1).
##' In general this should not be used (generate keys yourself with
##' `ssh-keygen` at the command line.  However this is useful for
##' testing and demonstration so I have included it to make that
##' easier.  Once a keypair has been generated it can be used with
##' [cyphr::keypair_openssl()].
##'
##' @title Create ssh keypairs
##' @param path A directory in which to create a keypair.  If the path
##'   does not exist it will be created.
##' @param password The password for the key.  The default will prompt
##'   interactively (but without echoing the password).  Other valid
##'   options are `FALSE` (no password) or a string.
##' @param use_shell Try to use `ssh-keygen` (the shell utility)
##'   rather than functions in the `openssl` package.  This will
##'   be necessary on at least very old versions of OS/X (Yosemite and
##'   older at least) where the keys generated by the `openssl`
##'   package cannot be read by the system ssh commands (e.g.,
##'   `ssh-add`).
##' @return The `path`, invisibly.  This is useful in the case
##'   where `path` is [tempfile()].
##' @export
##' @examples
##' # Generate a new key in a temporary directory:
##' path <- cyphr::ssh_keygen(password = FALSE)
##' dir(path) # will contain id_rsa and id_rsa.pub
##'
##' # This key can now be used via keypair_openssl:
##' key <- cyphr::keypair_openssl(path, path)
##' secret <- cyphr::encrypt_string("hello", key)
##' cyphr::decrypt_string(secret, key)
##'
##' # Cleanup
##' unlink(path, recursive = TRUE)
ssh_keygen <- function(path = tempfile(), password = TRUE, use_shell = FALSE) {
  if (file.exists(path) && !is_directory(path)) {
    stop("path exists but is not a directory")
  }
  dir.create(path, FALSE, TRUE)
  dest_key <- file.path(path, "id_rsa")
  dest_pub <- file.path(path, "id_rsa.pub")
  if (file.exists(dest_key)) {
    stop("private key ", dest_key, " exists already -- not overwriting")
  }
  if (file.exists(dest_pub)) {
    stop("public key ", dest_pub, " exists already -- not overwriting")
  }

  if (isTRUE(password)) {
    pw <- get_password_str(TRUE, "Enter passphrase: ")
  } else if (identical(password, FALSE)) {
    pw <- ""
  } else if (is.character(password)) {
    pw <- password
  } else {
    stop("Invalid input for password")
  }

  if (!nzchar(pw)) {
    pw <- if (use_shell) "''" else NULL
  }

  if (use_shell) {
    ssh_keygen <- sys_which("ssh-keygen")
    code <- system2(ssh_keygen, c("-q", "-N", pw, "-f", dest_key))
    if (code != 0L) {
      ## This can't be easily triggered so I'll leave this be for now.
      stop("Error running ssh-keygen") # nocov
    }
  } else {
    key <- openssl::rsa_keygen()
    pub <- as.list(key)$pubkey
    openssl::write_pem(key, dest_key, password = pw)
    Sys.chmod(dest_key, "600")
    openssl::write_ssh(pub, dest_pub)
  }

  invisible(path)
}
richfitz/cyphr documentation built on Aug. 23, 2023, 8:01 a.m.