Skip to content
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

support for encrypted PEM keys #128

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 95 additions & 16 deletions src/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

#include <limits.h> /* INT_MAX INT_MIN LLONG_MAX LLONG_MIN UCHAR_MAX ULLONG_MAX */
#include <stdint.h> /* uintptr_t */
#include <string.h> /* memset(3) strerror_r(3) */
#include <string.h> /* memset(3) strerror_r(3) strlen(3) strncpy(3) */
#include <math.h> /* INFINITY fabs(3) floor(3) frexp(3) fmod(3) round(3) isfinite(3) */
#include <time.h> /* struct tm time_t strptime(3) time(2) */
#include <ctype.h> /* isdigit(3), isxdigit(3), tolower(3) */
Expand Down Expand Up @@ -4062,10 +4062,42 @@ static BIO *getbio(lua_State *L) {
} /* getbio() */


/*
* PEM password callback for openssl
*
* Expects nil, string, or function on top of the stack. Errors from the
* user-provided function are not reported. Leaves one item on the top of the
* stack: nil, the original string, or the return value.
*
* This callback may be called twice by pk_new when the PEM key is encrypted
* and its type is not specified. The user-provided function is called only
* once because it gets replaced on the stack by the return value. This
* callback may not be called at all if the supplied PEM key is not encrypted.
*/
static int pem_pw_cb(char *buf, int size, int rwflag, void *u) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth adding a comment of "expects nil/function/string on top of the stack" and "leaves one item on the top of the stack".

"errors from the function are not reported"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps it should leave nothing on the stack?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requested comments added.

lua_State *L = (lua_State *) u;

if (lua_isfunction(L, -1) && lua_pcall(L, 0, 1, 0)) {
lua_pop(L, 1);
lua_pushnil(L);
}

if (lua_isnil(L, -1))
return 0;

const char *pass = lua_tostring(L, -1);
if (!pass)
return 0;

strncpy(buf, pass, size);
return MIN(strlen(pass), (unsigned int) size);
daurnimator marked this conversation as resolved.
Show resolved Hide resolved
} /* pem_pw_cb() */


static int pk_new(lua_State *L) {
EVP_PKEY **ud;

/* #1 table or key; if key, #2 format and #3 type */
/* #1 table or key; if key, #2 option table or format; if format, #3 type */
lua_settop(L, 3);

if (lua_istable(L, 1) || lua_isnil(L, 1)) {
Expand Down Expand Up @@ -4299,33 +4331,53 @@ static int pk_new(lua_State *L) {
#endif
} /* switch() */
} else if (lua_isstring(L, 1)) {
int type = optencoding(L, 2, "*", X509_ANY|X509_PEM|X509_DER);
int format;
int pubonly = 0, prvtonly = 0;
const char *opt, *data;
const char *type, *data;
size_t len;
BIO *bio;
EVP_PKEY *pub = NULL, *prvt = NULL;
int goterr = 0;

if (lua_istable(L, 2)) {
lua_pop(L, 1);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be popping the table itself; or the 3rd argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is always the 3rd argument because stack size is set to 3 on line 4101. Of course, this can be changed to lua_remove if you prefer it that way.

lua_getfield(L, 2, "format");
lua_getfield(L, 2, "type");
lua_getfield(L, 2, "password");
lua_remove(L, 2);
} else
lua_pushnil(L);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch only pushes one item vs the other branch's 3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the net amount of pushed items is 1 in both branches.


/* #1 key, #2 format, #3 type, #4 password or callback */

data = luaL_checklstring(L, 1, &len);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the error message for this will have the wrong index

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should it be then? This was just moved from line 4321 to process the arguments in ascending order. The purpose was to improve readability.

format = optencoding(L, 2, "*", X509_ANY|X509_PEM|X509_DER);

/* check if specified publickey or privatekey */
if ((opt = luaL_optstring(L, 3, NULL))) {
if (xtolower(opt[0]) == 'p' && xtolower(opt[1]) == 'u') {
if ((type = luaL_optstring(L, 3, NULL))) {
if (xtolower(type[0]) == 'p' && xtolower(type[1]) == 'u') {
pubonly = 1;
} else if (xtolower(opt[0]) == 'p' && xtolower(opt[1]) == 'r') {
} else if (xtolower(type[0]) == 'p' && xtolower(type[1]) == 'r') {
prvtonly = 1;
} else {
return luaL_argerror(L, 3, lua_pushfstring(L, "invalid option %s", opt));
return luaL_error(L, "invalid key type: %s", type);
}
}

data = luaL_checklstring(L, 1, &len);
if (!lua_isnil(L, 4)) {
if (format == X509_DER)
return luaL_error(L, "decryption supported only for PEM keys");
else format = X509_PEM;
}

ud = prepsimple(L, PKEY_CLASS);

if (!(bio = BIO_new_mem_buf((void *)data, len)))
return auxL_error(L, auxL_EOPENSSL, "pkey.new");

if (type == X509_PEM || type == X509_ANY) {
if (format == X509_PEM || format == X509_ANY) {
lua_pushvalue(L, 4);

if (!prvtonly && !pub) {
/*
* BIO_reset is a rewind for read-only
Expand All @@ -4334,19 +4386,21 @@ static int pk_new(lua_State *L) {
*/
BIO_reset(bio);

if (!(pub = PEM_read_bio_PUBKEY(bio, NULL, 0, "")))
if (!(pub = PEM_read_bio_PUBKEY(bio, NULL, pem_pw_cb, L)))
goterr = 1;
}

if (!pubonly && !prvt) {
BIO_reset(bio);

if (!(prvt = PEM_read_bio_PrivateKey(bio, NULL, 0, "")))
if (!(prvt = PEM_read_bio_PrivateKey(bio, NULL, pem_pw_cb, L)))
goterr = 1;
}

lua_pop(L, 1);
}

if (type == X509_DER || type == X509_ANY) {
if (format == X509_DER || format == X509_ANY) {
if (!prvtonly && !pub) {
BIO_reset(bio);

Expand Down Expand Up @@ -4675,14 +4729,27 @@ static int pk_toPEM(lua_State *L) {
bio = getbio(L);

for (i = 2; i <= top; i++) {
static const char *const opts[] = {
static const char *const types[] = {
"public", "PublicKey",
"private", "PrivateKey",
// "params", "Parameters",
NULL,
};
int type;
const char *cname = NULL;
const EVP_CIPHER *cipher = NULL;

if (lua_istable(L, i)) {
loadfield(L, i, "cipher", LUA_TSTRING, &cname);
if (!getfield(L, i, "type"))
lua_pushstring(L, cname ? "private" : "public");
} else
lua_pushvalue(L, i);

type = auxL_checkoption(L, -1, NULL, types, 1);
lua_pop(L, 1);

switch (auxL_checkoption(L, i, NULL, opts, 1)) {
switch (type) {
case 0: case 1: /* public, PublicKey */
if (!PEM_write_bio_PUBKEY(bio, key))
return auxL_error(L, auxL_EOPENSSL, "pkey:__tostring");
Expand All @@ -4693,9 +4760,21 @@ static int pk_toPEM(lua_State *L) {

break;
case 2: case 3: /* private, PrivateKey */
if (!PEM_write_bio_PrivateKey(bio, key, 0, 0, 0, 0, 0))
if (cname) {
cipher = EVP_get_cipherbyname(cname);
if (!cipher)
return luaL_error(L, "pkey:toPEM: unknown cipher: %s", cname);
if (!getfield(L, i, "password"))
return luaL_error(L, "pkey:toPEM: password not defined");
}
else
lua_pushnil(L);

if (!PEM_write_bio_PrivateKey(bio, key, cipher, NULL, 0, pem_pw_cb, L))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there is no cname on the stack then what will be at top of stack?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an else branch which pushes a nil value. The value will not be used because openssl will not invoke the callback when cipher equals NULL.

return auxL_error(L, auxL_EOPENSSL, "pkey:__tostring");

lua_pop(L, 1);

len = BIO_get_mem_data(bio, &pem);
lua_pushlstring(L, pem, len);
BIO_reset(bio);
Expand Down