Skip to content

Commit

Permalink
Merge branch 'feat_keywriter' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
bpvgoncalves committed Sep 16, 2023
2 parents afd2fe2 + 196e901 commit 9b25d07
Show file tree
Hide file tree
Showing 9 changed files with 483 additions and 4 deletions.
2 changes: 2 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ Description: This package makes available to R users some of the latest
and digital signatures.
License: GPL (>= 3)
Suggests:
base64enc,
cli,
knitr,
openssl,
rmarkdown,
testthat (>= 3.0.0)
LinkingTo:
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ export(encap_kyber)
export(keygen_dilithium)
export(keygen_kyber)
export(keygen_sphincs)
export(open_key)
export(sign_dilithium)
export(sign_sphincs)
export(verify_dilithium)
export(verify_sphincs)
export(write_key)
useDynLib(pqcrypto, .registration = TRUE)
13 changes: 9 additions & 4 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# pqcrypto (development version)

* Internals:
- Some changes to classes, new tests, lib compilation, ...
- Change data types from integer to raw.
##### New Features
- Specialized `print()` for pqcrypto_* objects
- Add new signing algorithm: Sphincs+
- Add `write_key()` and `open_key()` to key saving and retrieval

##### Internals
- Some changes to classes, new tests, lib compilation, ...
- Change data types from integer to raw

# pqcrypto 0.1.0

* Initial public version.
- Initial public version.
62 changes: 62 additions & 0 deletions R/key_open.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@

#' Key Management - Open
#'
#' Opens a private key or a public key file generated by `write_key()`.
#'
#' @param file_name The file containing a key to be opened.
#' @param password If the `file_name`contains an encrypted private key, the decryption password.
#'
#' @return The public or private key.
#'
#' @export
#'
#' @examples
#' key <- keygen_kyber()
#' path <- tempdir()
#' write_key(key$private, path, "myPassword")
#' prv <- open_key(paste0(path, "/keypair"), "myPassword")
#'
#' identical(key$private, prv)
#'
open_key <- function(file_name, password = NULL) {

b64_to_raw <- function(txt) {
if (requireNamespace("openssl", quietly = TRUE)) {
key <- openssl::base64_decode(txt)
} else if (requireNamespace("base64enc", quietly = TRUE)) {
key <- base64enc::base64decode(txt)
} else {
pq_msg(c(x="Unable to read the keys if openssl or base64enc packages are not present."))
return(NULL)
}
invisible(key)
}

if(!file.exists(file_name)) {
pq_stop(c(x = "Invalid 'file_name'."))
}
keydata <- readLines(file_name)
base64text <- paste0(keydata[2:(length(keydata)-1)], collapse = "\n")

if (keydata[1] %in% c("-----BEGIN PUBLIC KEY-----", "-----BEGIN PRIVATE KEY-----")) {
raw_key <- b64_to_raw(base64text)

} else if (keydata[1] == "-----BEGIN ENCRYPTED PRIVATE KEY-----") {
if (!is.null(password)) {
if (requireNamespace("openssl", quietly = TRUE)) {
raw_data <- unserialize(b64_to_raw(base64text))
raw_key <- openssl::aes_cbc_decrypt(raw_data, openssl::sha256(charToRaw(password)))
} else {
pq_msg(c(x="Reading encrypted private key files requires openssl package."))
return(NULL)
}
} else {
pq_stop(c(x="Reading encrypted private key files requires `password` argument to be provided."))
}
} else {
pq_stop(c(x="File does not look like a key file.",
i="Make sure the `file_name` argument points to a file created by `write_key()`."))
}

unserialize(raw_key)
}
120 changes: 120 additions & 0 deletions R/key_write.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@

#' Key Management - Save
#'
#' Saves a key-pair, a private key or a public key as a text file.
#' **NOTE**: While the text file resembles a PEM encoded key, current implementation is not strictly
#' following RFC7468 (Textual Encodings of PKIX, PKCS, and CMS Structures), RFC5280 (Internet X.509
#' Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile) and RFC5958
#' (Asymmetric Key Packages).
#'
#' @param key A key-pair produced by `keygen_kyber()`, `keygen_dilithium()` or
#' `keygen_sphincs()`, or a private key or public key belonging to such a key-pair.
#' @param path A path where the text file(s) containing the key(s) should be saved.
#' Defaults to the temporary directory. If NULL, the resulting encoded key is printed to the
#' console.
#' @param password A password to be used for private key encryption. If not provided, which is
#' **not advisable** when saving private keys (or the full key-pair) because the private key will
#' not be encrypted which is **potentially unsafe**.
#'
#' @return Invisibly the encoded key. Saves it to a file or displays it on the console.
#'
#' @export
#'
#' @examples
#' key <- keygen_sphincs()
#' write_key(key$public, NULL) # NULL used here to force console output instead of file saving
#'
write_key <- function(key, path = tempdir(), password = NULL) {

raw_to_b64 <- function(k) {
if (requireNamespace("openssl", quietly = TRUE)) {
base64text <- openssl::base64_encode(serialize(k, NULL), TRUE)
} else if (requireNamespace("base64enc", quietly = TRUE)) {
base64text <- paste0(base64enc::base64encode(serialize(k, NULL), 64, "\n"), "\n")
} else {
pq_msg(c(x="Unable to write keys to file if openssl or base64enc packages are not present."))
return(NULL)
}
invisible(base64text)
}

public <- function(k) {
paste0("-----BEGIN PUBLIC KEY-----\n",
raw_to_b64(k),
"-----END PUBLIC KEY-----\n")
}

private <- function(k) {
paste0("-----BEGIN PRIVATE KEY-----\n",
raw_to_b64(k),
"-----END PRIVATE KEY-----\n")
}

private_enc <- function(k, password) {
if (requireNamespace("openssl", quietly = TRUE)) {
enc <- openssl::aes_cbc_encrypt(serialize(k, NULL),
openssl::sha256(charToRaw(password)))
} else {
pq_msg(c(x="Private key encryption requires openssl package."))
return(NULL)
}
paste0("-----BEGIN ENCRYPTED PRIVATE KEY-----\n",
raw_to_b64(enc),
"-----END ENCRYPTED PRIVATE KEY-----\n")
}

if(!is.null(path) && !dir.exists(path)) {
pq_stop(c(x = "Unable to find the specified 'path' to write into."))
}

if (inherits(key, "pqcrypto_keypair")) {
if (!is.null(password)) {
cert_prv <- private_enc(key$private, as.character(password))
} else {
cert_prv <- private(key$private)
}
cert_pub <- public(key$public)

if (is.null(path)) {
cat(cert_prv, "\n", cert_pub, sep = "")
} else {
ff <- file(paste0(path, "/keypair"), "wt")
cat(cert_prv, file = ff)
close(ff)
ff <- file(paste0(path, "/keypair.pub"), "wt")
cat(cert_pub, file = ff)
close(ff)
}

} else if (inherits(key, "pqcrypto_private_key")) {
if (!is.null(password)) {
cert_prv <- private_enc(key, password)
} else {
cert_prv <- private(key)
}
if (is.null(path)) {
cat(cert_prv)
} else {
ff <- file(paste0(path, "/keypair"), "wt")
cat(cert_prv, file = ff)
close(ff)
}
invisible(cert_prv)

} else if (inherits(key, "pqcrypto_public_key")) {
cert_pub <- public(key)
if (is.null(path)) {
cat(cert_pub)
} else {
ff <- file(paste0(path, "/keypair.pub"), "wt")
cat(cert_pub, file = ff)
close(ff)
}
invisible(cert_pub)

} else {
pq_stop("Invalid object type.")
}

}

28 changes: 28 additions & 0 deletions man/open_key.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions man/write_key.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 97 additions & 0 deletions tests/testthat/test-key_open.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
test_that("Read Kyber key-pair", {

key <- keygen_kyber()
path <- tempdir()

write_key(key, path)
prv <- open_key(paste0(path, "/keypair"))
pub <- open_key(paste0(path, "/keypair.pub"))
expect_identical(key$private, prv)
expect_identical(key$public, pub)
unlink(paste0(path, "/keypair"))
unlink(paste0(path, "/keypair.pub"))

write_key(key, path, "mypass")
prv <- open_key(paste0(path, "/keypair"), "mypass")
pub <- open_key(paste0(path, "/keypair.pub"), "mypass")
expect_identical(key$private, prv)
expect_identical(key$public, pub)

expect_error(open_key(paste0(path, "/keypair")))
expect_error(open_key(paste0(path, "/keypair"), "notmypass"))
expect_error(open_key("/invalid_path/keypair", "mypass"))
expect_error(open_key("/invalid_path/keypair.pub", "mypass"))

unlink(paste0(path, "/keypair"))
unlink(paste0(path, "/keypair.pub"))

})

test_that("Read Dilithium key-pair", {

key <- keygen_dilithium()
path <- tempdir()

write_key(key, path)
prv <- open_key(paste0(path, "/keypair"))
pub <- open_key(paste0(path, "/keypair.pub"))
expect_identical(key$private, prv)
expect_identical(key$public, pub)
unlink(paste0(path, "/keypair"))
unlink(paste0(path, "/keypair.pub"))

write_key(key, path, "mypass")
prv <- open_key(paste0(path, "/keypair"), "mypass")
pub <- open_key(paste0(path, "/keypair.pub"), "mypass")
expect_identical(key$private, prv)
expect_identical(key$public, pub)

expect_error(open_key(paste0(path, "/keypair")))
expect_error(open_key(paste0(path, "/keypair"), "notmypass"))
expect_error(open_key("/invalid_path/keypair", "mypass"))
expect_error(open_key("/invalid_path/keypair.pub", "mypass"))

unlink(paste0(path, "/keypair"))
unlink(paste0(path, "/keypair.pub"))

})

test_that("Read Sphincs+ key-pair", {

key <- keygen_sphincs()
path <- tempdir()

write_key(key, path)
prv <- open_key(paste0(path, "/keypair"))
pub <- open_key(paste0(path, "/keypair.pub"))
expect_identical(key$private, prv)
expect_identical(key$public, pub)
unlink(paste0(path, "/keypair"))
unlink(paste0(path, "/keypair.pub"))

write_key(key, path, "mypass")
prv <- open_key(paste0(path, "/keypair"), "mypass")
pub <- open_key(paste0(path, "/keypair.pub"), "mypass")
expect_identical(key$private, prv)
expect_identical(key$public, pub)

expect_error(open_key(paste0(path, "/keypair")))
expect_error(open_key(paste0(path, "/keypair"), "notmypass"))
expect_error(open_key("/invalid_path/keypair", "mypass"))
expect_error(open_key("/invalid_path/keypair.pub", "mypass"))

unlink(paste0(path, "/keypair"))
unlink(paste0(path, "/keypair.pub"))

})

test_that("Fails on bad file", {

file <- tempfile()
writeLines("qwerty", file(file))

expect_error(open_key(file))

unlink(file)

})
Loading

0 comments on commit 9b25d07

Please sign in to comment.