Created
July 3, 2020 13:33
-
-
Save sharonovd/c93a4214fba39585e53644e499ace3dc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- RSA bindings for Tarantool | |
--- Carefully adapted from https://github.com/spacewander/lua-resty-rsa | |
local bit = require "bit" | |
local band = bit.band | |
local ffi = require "ffi" | |
local ffi_new = ffi.new | |
local ffi_gc = ffi.gc | |
local ffi_copy = ffi.copy | |
local ffi_str = ffi.string | |
local C = ffi.C | |
local tab_concat = table.concat | |
local setmetatable = setmetatable | |
-- remember to remove the '-dev' suffix when releasing a new version to opm | |
local _M = { _VERSION = '0.06-dev' } | |
local mt = { __index = _M } | |
local PADDING = { | |
RSA_PKCS1_PADDING = 1, -- RSA_size - 11 | |
RSA_SSLV23_PADDING = 2, -- RSA_size - 11 | |
RSA_NO_PADDING = 3, -- RSA_size | |
RSA_PKCS1_OAEP_PADDING = 4, -- RSA_size - 42 | |
} | |
_M.PADDING = PADDING | |
local KEY_TYPE = { | |
PKCS1 = "PKCS#1", | |
PKCS8 = "PKCS#8", | |
} | |
_M.KEY_TYPE = KEY_TYPE | |
ffi.cdef[[ | |
typedef struct bio_st BIO; | |
typedef struct bio_method_st BIO_METHOD; | |
BIO_METHOD *BIO_s_mem(void); | |
BIO * BIO_new(BIO_METHOD *type); | |
int BIO_puts(BIO *bp, const char *buf); | |
void BIO_vfree(BIO *a); | |
typedef struct rsa_st RSA; | |
RSA *RSA_new(void); | |
void RSA_free(RSA *rsa); | |
typedef int pem_password_cb(char *buf, int size, int rwflag, void *userdata); | |
RSA * PEM_read_bio_RSAPrivateKey(BIO *bp, RSA **rsa, pem_password_cb *cb, | |
void *u); | |
RSA * PEM_read_bio_RSAPublicKey(BIO *bp, RSA **rsa, pem_password_cb *cb, | |
void *u); | |
RSA * PEM_read_bio_RSA_PUBKEY(BIO *bp, RSA **rsa, pem_password_cb *cb, | |
void *u); | |
unsigned long ERR_get_error_line_data(const char **file, int *line, | |
const char **data, int *flags); | |
const char * ERR_reason_error_string(unsigned long e); | |
typedef struct bignum_st BIGNUM; | |
BIGNUM *BN_new(void); | |
void BN_free(BIGNUM *a); | |
typedef unsigned long BN_ULONG; | |
int BN_set_word(BIGNUM *a, BN_ULONG w); | |
typedef struct bn_gencb_st BN_GENCB; | |
int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb); | |
typedef struct evp_cipher_st EVP_CIPHER; | |
int PEM_write_bio_RSAPrivateKey(BIO *bp, RSA *x, const EVP_CIPHER *enc, | |
unsigned char *kstr, int klen, | |
pem_password_cb *cb, void *u); | |
int PEM_write_bio_RSAPublicKey(BIO *bp, RSA *x); | |
int PEM_write_bio_RSA_PUBKEY(BIO *bp, RSA *x); | |
long BIO_ctrl(BIO *bp, int cmd, long larg, void *parg); | |
int BIO_read(BIO *b, void *data, int len); | |
typedef struct evp_pkey_st EVP_PKEY; | |
typedef struct engine_st ENGINE; | |
typedef struct evp_pkey_ctx_st EVP_PKEY_CTX; | |
EVP_PKEY *EVP_PKEY_new(void); | |
void EVP_PKEY_free(EVP_PKEY *key); | |
EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e); | |
void EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); | |
int EVP_PKEY_CTX_ctrl(EVP_PKEY_CTX *ctx, int keytype, int optype, | |
int cmd, int p1, void *p2); | |
int EVP_PKEY_size(EVP_PKEY *pkey); | |
int EVP_PKEY_encrypt_init(EVP_PKEY_CTX *ctx); | |
int EVP_PKEY_encrypt(EVP_PKEY_CTX *ctx, | |
unsigned char *out, size_t *outlen, | |
const unsigned char *in, size_t inlen); | |
int EVP_PKEY_decrypt_init(EVP_PKEY_CTX *ctx); | |
int EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx, | |
unsigned char *out, size_t *outlen, | |
const unsigned char *in, size_t inlen); | |
int EVP_PKEY_set1_RSA(EVP_PKEY *pkey, RSA *key); | |
int PEM_write_bio_PKCS8PrivateKey(BIO *bp, EVP_PKEY *x, const EVP_CIPHER *enc, | |
char *kstr, int klen, pem_password_cb *cb, | |
void *u); | |
void OpenSSL_add_all_digests(void); | |
typedef struct env_md_st EVP_MD; | |
typedef struct env_md_ctx_st EVP_MD_CTX; | |
const EVP_MD *EVP_get_digestbyname(const char *name); | |
/* EVP_MD_CTX methods for OpenSSL < 1.1.0 */ | |
EVP_MD_CTX *EVP_MD_CTX_create(void); | |
void EVP_MD_CTX_destroy(EVP_MD_CTX *ctx); | |
/* EVP_MD_CTX methods for OpenSSL >= 1.1.0 */ | |
EVP_MD_CTX *EVP_MD_CTX_new(void); | |
void EVP_MD_CTX_free(EVP_MD_CTX *ctx); | |
int EVP_DigestInit(EVP_MD_CTX *ctx, const EVP_MD *type); | |
int EVP_DigestUpdate(EVP_MD_CTX *ctx, const unsigned char *in, int inl); | |
int EVP_SignFinal(EVP_MD_CTX *ctx,unsigned char *sig,unsigned int *s, | |
EVP_PKEY *pkey); | |
int EVP_VerifyFinal(EVP_MD_CTX *ctx,unsigned char *sigbuf, unsigned int siglen, | |
EVP_PKEY *pkey); | |
int EVP_PKEY_set1_RSA(EVP_PKEY *e, RSA *r); | |
void ERR_set_error_data(char *data, int flags); | |
]] | |
--[[ | |
# define EVP_PKEY_CTX_set_rsa_padding(ctx, pad) \ | |
EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, -1, EVP_PKEY_CTRL_RSA_PADDING, \ | |
pad, NULL) | |
# define EVP_SignInit(a,b) EVP_DigestInit(a,b) | |
# define EVP_SignUpdate(a,b,c) EVP_DigestUpdate(a,b,c) | |
--]] | |
local EVP_PKEY_ALG_CTRL = 0x1000 | |
local EVP_PKEY_CTRL_RSA_PADDING = EVP_PKEY_ALG_CTRL + 1 | |
local NID_rsaEncryption = 6 | |
local EVP_PKEY_RSA = NID_rsaEncryption | |
local ERR_TXT_STRING = 0x02 | |
local evp_md_ctx_new | |
local evp_md_ctx_free | |
if not pcall(function () return C.EVP_MD_CTX_create end) then | |
evp_md_ctx_new = C.EVP_MD_CTX_new | |
evp_md_ctx_free = C.EVP_MD_CTX_free | |
else | |
evp_md_ctx_new = C.EVP_MD_CTX_create | |
evp_md_ctx_free = C.EVP_MD_CTX_destroy | |
end | |
local function ssl_err() | |
local err_queue = {} | |
local i = 1 | |
local data = ffi_new("const char*[1]") | |
local flags = ffi_new("int[1]") | |
while true do | |
local code = C.ERR_get_error_line_data(nil, nil, data, flags) | |
if code == 0 then | |
break | |
end | |
local err = C.ERR_reason_error_string(code) | |
err_queue[i] = ffi_str(err) | |
i = i + 1 | |
if data[0] ~= nil and band(flags[0], ERR_TXT_STRING) > 0 then | |
err_queue[i] = ffi_str(data[0]) | |
i = i + 1 | |
end | |
end | |
return nil, tab_concat(err_queue, ": ", 1, i - 1) | |
end | |
local function read_bio(bio) | |
local BIO_CTRL_PENDING = 10 | |
local keylen = C.BIO_ctrl(bio, BIO_CTRL_PENDING, 0, nil); | |
local key = ffi.new("char[?]", keylen) | |
if C.BIO_read(bio, key, keylen) < 0 then | |
return ssl_err() | |
end | |
return ffi_str(key, keylen) | |
end | |
-- Follow the calling style to avoid careless mistake. | |
function _M.generate_rsa_keys(_, bits, pkcs8) | |
local rsa = C.RSA_new() | |
ffi_gc(rsa, C.RSA_free) | |
local bn = C.BN_new() | |
ffi_gc(bn, C.BN_free) | |
-- Set public exponent to 65537 | |
if C.BN_set_word(bn, 65537) ~= 1 then | |
return nil, ssl_err() | |
end | |
-- Generate key | |
if C.RSA_generate_key_ex(rsa, bits, bn, nil) ~= 1 then | |
return nil, ssl_err() | |
end | |
local pub_key_bio = C.BIO_new(C.BIO_s_mem()) | |
ffi_gc(pub_key_bio, C.BIO_vfree) | |
if pkcs8 == true then | |
if C.PEM_write_bio_RSA_PUBKEY(pub_key_bio, rsa) ~= 1 then | |
return nil, ssl_err() | |
end | |
else | |
if C.PEM_write_bio_RSAPublicKey(pub_key_bio, rsa) ~= 1 then | |
return nil, ssl_err() | |
end | |
end | |
local public_key, err = read_bio(pub_key_bio) | |
if not public_key then | |
return nil, nil, err | |
end | |
local priv_key_bio = C.BIO_new(C.BIO_s_mem()) | |
ffi_gc(priv_key_bio, C.BIO_vfree) | |
if pkcs8 == true then | |
local pk = C.EVP_PKEY_new() | |
ffi_gc(pk, C.EVP_PKEY_free) | |
if C.EVP_PKEY_set1_RSA(pk,rsa) ~= 1 then | |
return nil, ssl_err() | |
end | |
if C.PEM_write_bio_PKCS8PrivateKey(priv_key_bio, pk, | |
nil, nil, 0, nil, nil) ~= 1 then | |
return nil, ssl_err() | |
end | |
else | |
if C.PEM_write_bio_RSAPrivateKey(priv_key_bio, rsa, | |
nil, nil, 0, nil, nil) ~= 1 then | |
return nil, ssl_err() | |
end | |
end | |
local private_key | |
private_key, err = read_bio(priv_key_bio) | |
if not private_key then | |
return nil, nil, err | |
end | |
return public_key, private_key | |
end | |
function _M.new(_, opts) | |
local key, read_func, is_pub, md | |
if opts.public_key then | |
key = opts.public_key | |
if opts.key_type == KEY_TYPE.PKCS8 then | |
read_func = C.PEM_read_bio_RSA_PUBKEY | |
else | |
read_func = C.PEM_read_bio_RSAPublicKey | |
end | |
is_pub = true | |
elseif opts.private_key then | |
key = opts.private_key | |
read_func = C.PEM_read_bio_RSAPrivateKey | |
else | |
return nil, "public_key or private_key not found" | |
end | |
local bio_method = C.BIO_s_mem() | |
local bio = C.BIO_new(bio_method) | |
ffi_gc(bio, C.BIO_vfree) | |
local len = C.BIO_puts(bio, key) | |
if len < 0 then | |
return ssl_err() | |
end | |
local pass | |
if opts.password then | |
local plen = #opts.password | |
pass = ffi_new("unsigned char[?]", plen + 1) | |
ffi_copy(pass, opts.password, plen) | |
end | |
local rsa = read_func(bio, nil, nil, pass) | |
if rsa == nil then | |
return ssl_err() | |
end | |
ffi_gc(rsa, C.RSA_free) | |
-- EVP_PKEY | |
local pkey = C.EVP_PKEY_new() | |
ffi_gc(pkey, C.EVP_PKEY_free) | |
if C.EVP_PKEY_set1_RSA(pkey, rsa) == 0 then | |
return ssl_err() | |
end | |
--EVP_PKEY_CTX | |
local ctx = C.EVP_PKEY_CTX_new(pkey, nil) | |
if ctx == nil then | |
return ssl_err() | |
end | |
ffi_gc(ctx, C.EVP_PKEY_CTX_free) | |
-- md_ctx init for sign or verify; if signature algorithm is seted | |
if opts.algorithm then | |
md = C.EVP_get_digestbyname(opts.algorithm) | |
if md == nil then | |
return nil, "Unknown message digest" | |
end | |
end | |
-- ctx init for encrypt or decrypt | |
-- default for encrypt/decrypt if nothing is set | |
if opts.padding or not opts.digest then | |
local init_func = is_pub and C.EVP_PKEY_encrypt_init | |
or C.EVP_PKEY_decrypt_init | |
if init_func(ctx) <= 0 then | |
return ssl_err() | |
end | |
if C.EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, -1, EVP_PKEY_CTRL_RSA_PADDING, | |
opts.padding or PADDING.RSA_PKCS1_PADDING, nil) <= 0 then | |
return ssl_err() | |
end | |
end | |
local size = C.EVP_PKEY_size(pkey) | |
return setmetatable({ | |
pkey = pkey, | |
size = size, | |
buf = ffi_new("unsigned char[?]", size), | |
_encrypt_ctx = is_pub and ctx or nil, | |
_decrypt_ctx = not is_pub and ctx or nil, | |
is_pub = is_pub, | |
md = md, | |
}, mt) | |
end | |
function _M.decrypt(self, str) | |
local ctx = self._decrypt_ctx | |
if not ctx then | |
return nil, "not inited for decrypt" | |
end | |
local len = ffi_new("size_t [1]") | |
if C.EVP_PKEY_decrypt(ctx, nil, len, str, #str) <= 0 then | |
return ssl_err() | |
end | |
local buf = self.buf | |
if C.EVP_PKEY_decrypt(ctx, buf, len, str, #str) <= 0 then | |
return ssl_err() | |
end | |
return ffi_str(buf, len[0]) | |
end | |
function _M.encrypt(self, str) | |
local ctx = self._encrypt_ctx | |
if not ctx then | |
return nil, "not inited for encrypt" | |
end | |
local len = ffi_new("size_t [1]") | |
if C.EVP_PKEY_encrypt(ctx, nil, len, str, #str) <= 0 then | |
return ssl_err() | |
end | |
local buf = self.buf | |
if C.EVP_PKEY_encrypt(ctx, buf, len, str, #str) <= 0 then | |
return ssl_err() | |
end | |
return ffi_str(buf, len[0]) | |
end | |
function _M.sign(self, str) | |
if self.is_pub then | |
return nil, "not inited for sign" | |
end | |
local md_ctx = evp_md_ctx_new() | |
ffi_gc(md_ctx, evp_md_ctx_free) | |
if C.EVP_DigestInit(md_ctx, self.md) <= 0 then | |
return ssl_err() | |
end | |
if C.EVP_DigestUpdate(md_ctx, str, #str) <= 0 then | |
return ssl_err() | |
end | |
local buf = self.buf | |
local len = ffi_new("unsigned int[1]") | |
if C.EVP_SignFinal(md_ctx, self.buf, len, self.pkey) <= 0 then | |
return ssl_err() | |
end | |
return ffi_str(buf, len[0]) | |
end | |
function _M.verify(self, str, sig) | |
if not self.is_pub then | |
return nil, "not inited for verify" | |
end | |
local md_ctx = evp_md_ctx_new() | |
ffi_gc(md_ctx, evp_md_ctx_free) | |
if C.EVP_DigestInit(md_ctx, self.md) <= 0 then | |
return ssl_err() | |
end | |
if C.EVP_DigestUpdate(md_ctx, str, #str) <= 0 then | |
return ssl_err() | |
end | |
local siglen = #sig | |
local buf = siglen <= self.size and self.buf | |
or ffi_new("unsigned char[?]", siglen) | |
ffi_copy(buf, sig, siglen) | |
if C.EVP_VerifyFinal(md_ctx, buf, siglen, self.pkey) <= 0 then | |
return ssl_err() | |
end | |
return true | |
end | |
return _M |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment