-
Notifications
You must be signed in to change notification settings - Fork 49
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
base: master
Are you sure you want to change the base?
Changes from all commits
db281ae
24ff6da
2f246cc
312269d
1d6f159
53fd95f
89f26bb
4f41ae9
652de88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) */ | ||
|
@@ -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) { | ||
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)) { | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this could be popping the table itself; or the 3rd argument. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This branch only pushes one item vs the other branch's 3 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the error message for this will have the wrong index There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
@@ -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); | ||
|
||
|
@@ -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"); | ||
|
@@ -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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
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.
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"
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.
perhaps it should leave nothing on the stack?
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.
Requested comments added.