aboutsummaryrefslogtreecommitdiffstats
path: root/server/resty/evp.lua
diff options
context:
space:
mode:
Diffstat (limited to 'server/resty/evp.lua')
-rw-r--r--server/resty/evp.lua804
1 files changed, 804 insertions, 0 deletions
diff --git a/server/resty/evp.lua b/server/resty/evp.lua
new file mode 100644
index 0000000..584ff5a
--- /dev/null
+++ b/server/resty/evp.lua
@@ -0,0 +1,804 @@
+local ffi = require "ffi"
+local ffi_copy = ffi.copy
+local ffi_gc = ffi.gc
+local ffi_new = ffi.new
+local ffi_string = ffi.string
+local ffi_cast = ffi.cast
+local _C = ffi.C
+
+local _M = { _VERSION = "0.2.3" }
+
+local ngx = ngx
+
+
+local CONST = {
+ SHA256_DIGEST = "SHA256",
+ SHA512_DIGEST = "SHA512",
+ -- ref : https://github.com/openssl/openssl/blob/master/include/openssl/rsa.h
+ RSA_PKCS1_PADDING = 1,
+ RSA_SSLV23_PADDING = 2,
+ RSA_NO_PADDING = 3,
+ RSA_PKCS1_OAEP_PADDING = 4,
+ RSA_X931_PADDING = 5,
+ RSA_PKCS1_PSS_PADDING = 6,
+ -- ref : https://github.com/openssl/openssl/blob/master/include/openssl/evp.h
+ NID_rsaEncryption = 6,
+ EVP_PKEY_RSA = 6,
+ EVP_PKEY_ALG_CTRL = 0x1000,
+ EVP_PKEY_CTRL_RSA_PADDING = 0x1000 + 1,
+
+ EVP_PKEY_OP_TYPE_CRYPT = 768,
+ EVP_PKEY_CTRL_RSA_OAEP_MD = 0x1000 + 9
+}
+_M.CONST = CONST
+
+
+-- Reference: https://wiki.openssl.org/index.php/EVP_Signing_and_Verifying
+ffi.cdef[[
+// Error handling
+unsigned long ERR_get_error(void);
+const char * ERR_reason_error_string(unsigned long e);
+
+// Basic IO
+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);
+int BIO_write(BIO *b, const void *buf, int len);
+
+// RSA
+typedef struct rsa_st RSA;
+int RSA_size(const RSA *rsa);
+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);
+
+// EC_KEY
+typedef struct ec_key_st EC_KEY;
+void EC_KEY_free(EC_KEY *key);
+EC_KEY * PEM_read_bio_ECPrivateKey(BIO *bp, EC_KEY **key, pem_password_cb *cb,
+ void *u);
+EC_KEY * PEM_read_bio_ECPublicKey(BIO *bp, EC_KEY **key, pem_password_cb *cb,
+ void *u);
+// EVP PKEY
+typedef struct evp_pkey_st EVP_PKEY;
+typedef struct engine_st ENGINE;
+EVP_PKEY *EVP_PKEY_new(void);
+int EVP_PKEY_set1_RSA(EVP_PKEY *pkey,RSA *key);
+int EVP_PKEY_set1_EC_KEY(EVP_PKEY *pkey,EC_KEY *key);
+EVP_PKEY *EVP_PKEY_new_mac_key(int type, ENGINE *e,
+ const unsigned char *key, int keylen);
+void EVP_PKEY_free(EVP_PKEY *key);
+int i2d_RSA(RSA *a, unsigned char **out);
+
+// Additional typedef of ECC operations (DER/RAW sig conversion)
+typedef struct bignum_st BIGNUM;
+BIGNUM *BN_new(void);
+void BN_free(BIGNUM *a);
+int BN_num_bits(const BIGNUM *a);
+int BN_bn2bin(const BIGNUM *a, unsigned char *to);
+BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret);
+char *BN_bn2hex(const BIGNUM *a);
+
+
+typedef struct ECDSA_SIG_st {
+ BIGNUM *r;
+ BIGNUM *s;} ECDSA_SIG;
+ECDSA_SIG* ECDSA_SIG_new(void);
+int i2d_ECDSA_SIG(const ECDSA_SIG *sig, unsigned char **pp);
+ECDSA_SIG* d2i_ECDSA_SIG(ECDSA_SIG **sig, unsigned char **pp,
+long len);
+void ECDSA_SIG_free(ECDSA_SIG *sig);
+
+typedef struct ecgroup_st EC_GROUP;
+
+EC_GROUP *EC_KEY_get0_group(const EC_KEY *key);
+EC_KEY *EVP_PKEY_get0_EC_KEY(EVP_PKEY *pkey);
+int EC_GROUP_get_order(const EC_GROUP *group, BIGNUM *order, void *ctx);
+
+
+// PUBKEY
+EVP_PKEY *PEM_read_bio_PUBKEY(BIO *bp, EVP_PKEY **x,
+ pem_password_cb *cb, void *u);
+
+// X509
+typedef struct x509_st X509;
+X509 *PEM_read_bio_X509(BIO *bp, X509 **x, pem_password_cb *cb, void *u);
+EVP_PKEY * X509_get_pubkey(X509 *x);
+void X509_free(X509 *a);
+void EVP_PKEY_free(EVP_PKEY *key);
+int i2d_X509(X509 *a, unsigned char **out);
+X509 *d2i_X509_bio(BIO *bp, X509 **x);
+
+// X509 store
+typedef struct x509_store_st X509_STORE;
+typedef struct X509_crl_st X509_CRL;
+X509_STORE *X509_STORE_new(void );
+int X509_STORE_add_cert(X509_STORE *ctx, X509 *x);
+ // Use this if we want to load the certs directly from a variables
+int X509_STORE_add_crl(X509_STORE *ctx, X509_CRL *x);
+int X509_STORE_load_locations (X509_STORE *ctx,
+ const char *file, const char *dir);
+void X509_STORE_free(X509_STORE *v);
+
+// X509 store context
+typedef struct x509_store_ctx_st X509_STORE_CTX;
+X509_STORE_CTX *X509_STORE_CTX_new(void);
+int X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store,
+ X509 *x509, void *chain);
+int X509_verify_cert(X509_STORE_CTX *ctx);
+void X509_STORE_CTX_cleanup(X509_STORE_CTX *ctx);
+int X509_STORE_CTX_get_error(X509_STORE_CTX *ctx);
+const char *X509_verify_cert_error_string(long n);
+void X509_STORE_CTX_free(X509_STORE_CTX *ctx);
+
+// EVP Sign/Verify
+typedef struct env_md_ctx_st EVP_MD_CTX;
+typedef struct env_md_st EVP_MD;
+typedef struct evp_pkey_ctx_st EVP_PKEY_CTX;
+const EVP_MD *EVP_get_digestbyname(const char *name);
+
+//OpenSSL 1.0
+EVP_MD_CTX *EVP_MD_CTX_create(void);
+void EVP_MD_CTX_destroy(EVP_MD_CTX *ctx);
+
+//OpenSSL 1.1
+EVP_MD_CTX *EVP_MD_CTX_new(void);
+void EVP_MD_CTX_free(EVP_MD_CTX *ctx);
+
+int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl);
+int EVP_DigestSignInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx,
+ const EVP_MD *type, ENGINE *e, EVP_PKEY *pkey);
+int EVP_DigestUpdate(EVP_MD_CTX *ctx,const void *d,
+ size_t cnt);
+int EVP_DigestSignFinal(EVP_MD_CTX *ctx,
+ unsigned char *sigret, size_t *siglen);
+
+int EVP_DigestVerifyInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx,
+ const EVP_MD *type, ENGINE *e, EVP_PKEY *pkey);
+int EVP_DigestVerifyFinal(EVP_MD_CTX *ctx,
+ unsigned char *sig, size_t siglen);
+
+// Fingerprints
+int X509_digest(const X509 *data,const EVP_MD *type,
+ unsigned char *md, unsigned int *len);
+
+//EVP encrypt decrypt
+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);
+
+
+]]
+
+
+local function _err(ret)
+ -- The openssl error queue can have multiple items, print them all separated by ': '
+ local errs = {}
+ local code = _C.ERR_get_error()
+ while code ~= 0 do
+ table.insert(errs, 1, ffi_string(_C.ERR_reason_error_string(code)))
+ code = _C.ERR_get_error()
+ end
+
+ if #errs == 0 then
+ return ret, "Zero error code (null arguments?)"
+ end
+ return ret, table.concat(errs, ": ")
+end
+
+local ctx_new, ctx_free
+local openssl11, e = pcall(function ()
+ local ctx = _C.EVP_MD_CTX_new()
+ _C.EVP_MD_CTX_free(ctx)
+end)
+
+ngx.log(ngx.DEBUG, "openssl11=", openssl11, " err=", e)
+
+if openssl11 then
+ ctx_new = function ()
+ return _C.EVP_MD_CTX_new()
+ end
+ ctx_free = function (ctx)
+ ffi_gc(ctx, _C.EVP_MD_CTX_free)
+ end
+else
+ ctx_new = function ()
+ local ctx = _C.EVP_MD_CTX_create()
+ return ctx
+ end
+ ctx_free = function (ctx)
+ ffi_gc(ctx, _C.EVP_MD_CTX_destroy)
+ end
+end
+
+local function _new_key(self, opts)
+ local bio = _C.BIO_new(_C.BIO_s_mem())
+ ffi_gc(bio, _C.BIO_vfree)
+ if _C.BIO_puts(bio, opts.pem_private_key) < 0 then
+ return _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 key = nil
+ if self.algo == "RSA" then
+ key = _C.PEM_read_bio_RSAPrivateKey(bio, nil, nil, pass)
+ ffi_gc(key, _C.RSA_free)
+ elseif self.algo == "ECDSA" then
+ key = _C.PEM_read_bio_ECPrivateKey(bio, nil, nil, pass)
+ ffi_gc(key, _C.EC_KEY_free)
+ end
+
+ if not key then
+ return _err()
+ end
+
+ local evp_pkey = _C.EVP_PKEY_new()
+ if evp_pkey == nil then
+ return _err()
+ end
+
+ ffi_gc(evp_pkey, _C.EVP_PKEY_free)
+ if self.algo == "RSA" then
+ if _C.EVP_PKEY_set1_RSA(evp_pkey, key) ~= 1 then
+ return _err()
+ end
+ elseif self.algo == "ECDSA" then
+ if _C.EVP_PKEY_set1_EC_KEY(evp_pkey, key) ~= 1 then
+ return _err()
+ end
+ end
+
+ self.evp_pkey = evp_pkey
+ return self, nil
+end
+
+local function _create_evp_ctx(self, encrypt)
+ self.ctx = _C.EVP_PKEY_CTX_new(self.evp_pkey, nil)
+ if self.ctx == nil then
+ return _err()
+ end
+
+ ffi_gc(self.ctx, _C.EVP_PKEY_CTX_free)
+
+ local md = _C.EVP_get_digestbyname(self.digest_alg)
+ if ffi_cast("void *", md) == nil then
+ return nil, "Unknown message digest"
+ end
+
+ if encrypt then
+ if _C.EVP_PKEY_encrypt_init(self.ctx) <= 0 then
+ return _err()
+ end
+ else
+ if _C.EVP_PKEY_decrypt_init(self.ctx) <= 0 then
+ return _err()
+ end
+ end
+
+ if _C.EVP_PKEY_CTX_ctrl(self.ctx, CONST.EVP_PKEY_RSA, -1, CONST.EVP_PKEY_CTRL_RSA_PADDING,
+ self.padding, nil) <= 0 then
+ return _err()
+ end
+
+ if self.padding == CONST.RSA_PKCS1_OAEP_PADDING then
+ if _C.EVP_PKEY_CTX_ctrl(self.ctx, CONST.EVP_PKEY_RSA, CONST.EVP_PKEY_OP_TYPE_CRYPT,
+ CONST.EVP_PKEY_CTRL_RSA_OAEP_MD, 0, ffi_cast("void *", md)) <= 0 then
+ return _err()
+ end
+ end
+
+ return self.ctx
+end
+
+local RSASigner = {algo="RSA"}
+_M.RSASigner = RSASigner
+
+--- Create a new RSASigner
+-- @param pem_private_key A private key string in PEM format
+-- @param password password for the private key (if required)
+-- @returns RSASigner, err_string
+function RSASigner.new(self, pem_private_key, password)
+ return _new_key (
+ self,
+ {
+ pem_private_key = pem_private_key,
+ password = password
+ }
+ )
+end
+
+
+--- Sign a message
+-- @param message The message to sign
+-- @param digest_name The digest format to use (e.g., "SHA256")
+-- @returns signature, error_string
+function RSASigner.sign(self, message, digest_name)
+ local buf = ffi_new("unsigned char[?]", 1024)
+ local len = ffi_new("size_t[1]", 1024)
+
+ local ctx = ctx_new()
+ if ctx == nil then
+ return _err()
+ end
+ ctx_free(ctx)
+
+ local md = _C.EVP_get_digestbyname(digest_name)
+ if md == nil then
+ return _err()
+ end
+
+ if _C.EVP_DigestInit_ex(ctx, md, nil) ~= 1 then
+ return _err()
+ end
+
+ local ret = _C.EVP_DigestSignInit(ctx, nil, md, nil, self.evp_pkey)
+ if ret ~= 1 then
+ return _err()
+ end
+ if _C.EVP_DigestUpdate(ctx, message, #message) ~= 1 then
+ return _err()
+ end
+ if _C.EVP_DigestSignFinal(ctx, buf, len) ~= 1 then
+ return _err()
+ end
+ return ffi_string(buf, len[0]), nil
+end
+
+
+local ECSigner = {algo="ECDSA"}
+_M.ECSigner = ECSigner
+
+--- Create a new ECSigner
+-- @param pem_private_key A private key string in PEM format
+-- @param password password for the private key (if required)
+-- @returns ECSigner, err_string
+function ECSigner.new(self, pem_private_key, password)
+ return RSASigner.new(self, pem_private_key, password)
+end
+
+--- Sign a message with ECDSA
+-- @param message The message to sign
+-- @param digest_name The digest format to use (e.g., "SHA256")
+-- @returns signature, error_string
+function ECSigner.sign(self, message, digest_name)
+ return RSASigner.sign(self, message, digest_name)
+end
+
+--- Converts a ASN.1 DER signature to RAW r,s
+-- @param signature The ASN.1 DER signature
+-- @returns signature, error_string
+function ECSigner.get_raw_sig(self, signature)
+ if not signature then
+ return nil, "Must pass a signature to convert"
+ end
+ local sig_ptr = ffi_new("unsigned char *[1]")
+ local sig_bin = ffi_new("unsigned char [?]", #signature)
+ ffi_copy(sig_bin, signature, #signature)
+
+ sig_ptr[0] = sig_bin
+ local sig = _C.d2i_ECDSA_SIG(nil, sig_ptr, #signature)
+ ffi_gc(sig, _C.ECDSA_SIG_free)
+
+ local rbytes = math.floor((_C.BN_num_bits(sig.r)+7)/8)
+ local sbytes = math.floor((_C.BN_num_bits(sig.s)+7)/8)
+
+ -- Ensure we copy the BN in a padded form
+ local ec = _C.EVP_PKEY_get0_EC_KEY(self.evp_pkey)
+ local ecgroup = _C.EC_KEY_get0_group(ec)
+
+ local order = _C.BN_new()
+ ffi_gc(order, _C.BN_free)
+
+ -- res is an int, if 0, curve not found
+ local res = _C.EC_GROUP_get_order(ecgroup, order, nil)
+
+ -- BN_num_bytes is a #define, so have to use BN_num_bits
+ local order_size_bytes = math.floor((_C.BN_num_bits(order)+7)/8)
+ local resbuf_len = order_size_bytes *2
+ local resbuf = ffi_new("unsigned char[?]", resbuf_len)
+
+ -- Let's whilst preserving MSB
+ _C.BN_bn2bin(sig.r, resbuf + order_size_bytes - rbytes)
+ _C.BN_bn2bin(sig.s, resbuf + (order_size_bytes*2) - sbytes)
+
+ local raw = ffi_string(resbuf, resbuf_len)
+ return raw, nil
+end
+
+local RSAVerifier = {}
+_M.RSAVerifier = RSAVerifier
+
+
+--- Create a new RSAVerifier
+-- @param key_source An instance of Cert or PublicKey used for verification
+-- @returns RSAVerifier, error_string
+function RSAVerifier.new(self, key_source)
+ if not key_source then
+ return nil, "You must pass in an key_source for a public key"
+ end
+ local evp_public_key = key_source.public_key
+ self.evp_pkey = evp_public_key
+ return self, nil
+end
+
+--- Verify a message is properly signed
+-- @param message The original message
+-- @param the signature to verify
+-- @param digest_name The digest type that was used to sign
+-- @returns bool, error_string
+function RSAVerifier.verify(self, message, sig, digest_name)
+ local md = _C.EVP_get_digestbyname(digest_name)
+ if md == nil then
+ return _err(false)
+ end
+
+ local ctx = ctx_new()
+ if ctx == nil then
+ return _err(false)
+ end
+ ctx_free(ctx)
+
+ if _C.EVP_DigestInit_ex(ctx, md, nil) ~= 1 then
+ return _err(false)
+ end
+
+ local ret = _C.EVP_DigestVerifyInit(ctx, nil, md, nil, self.evp_pkey)
+ if ret ~= 1 then
+ return _err(false)
+ end
+ if _C.EVP_DigestUpdate(ctx, message, #message) ~= 1 then
+ return _err(false)
+ end
+ local sig_bin = ffi_new("unsigned char[?]", #sig)
+ ffi_copy(sig_bin, sig, #sig)
+ if _C.EVP_DigestVerifyFinal(ctx, sig_bin, #sig) == 1 then
+ return true, nil
+ else
+ return false, "Verification failed"
+ end
+end
+
+local ECVerifier = {}
+_M.ECVerifier = ECVerifier
+--- Create a new ECVerifier
+-- @param key_source An instance of Cert or PublicKey used for verification
+-- @returns ECVerifier, error_string
+function ECVerifier.new(self, key_source)
+ return RSAVerifier.new(self, key_source)
+end
+
+--- Verify a message is properly signed
+-- @param message The original message
+-- @param the signature to verify
+-- @param digest_name The digest type that was used to sign
+-- @returns bool, error_string
+function ECVerifier.verify(self, message, sig, digest_name)
+ -- We have to convert the signature back from RAW to ASN1 for verification
+ local der_sig, err = self:get_der_sig(sig)
+ if not der_sig then
+ return nil, err
+ end
+ return RSAVerifier.verify(self, message, der_sig, digest_name)
+end
+
+--- Converts a RAW r,s signature to ASN.1 DER signature (ECDSA)
+-- @param signature The raw signature
+-- @returns signature, error_string
+function ECVerifier.get_der_sig(self, signature)
+ if not signature then
+ return nil, "Must pass a signature to convert"
+ end
+ -- inspired from https://bit.ly/2yZxzxJ
+ local ec = _C.EVP_PKEY_get0_EC_KEY(self.evp_pkey)
+ local ecgroup = _C.EC_KEY_get0_group(ec)
+
+ local order = _C.BN_new()
+ ffi_gc(order, _C.BN_free)
+
+ -- res is an int, if 0, curve not found
+ local res = _C.EC_GROUP_get_order(ecgroup, order, nil)
+
+ -- BN_num_bytes is a #define, so have to use BN_num_bits
+ local order_size_bytes = math.floor((_C.BN_num_bits(order)+7)/8)
+
+ if #signature ~= 2 * order_size_bytes then
+ return nil, "signature length != 2 * order length"
+ end
+
+ local sig_bytes = ffi_new("unsigned char [?]", #signature)
+ ffi_copy(sig_bytes, signature, #signature)
+ local ecdsa = _C.ECDSA_SIG_new()
+ ffi_gc(ecdsa, _C.ECDSA_SIG_free)
+
+ -- Those do not need to be GCed as they are cleared by the ECDSA_SIG_free()
+ local r = _C.BN_bin2bn(sig_bytes, order_size_bytes, nil)
+ local s = _C.BN_bin2bn(sig_bytes + order_size_bytes, order_size_bytes, nil)
+
+ ecdsa.r = r
+ ecdsa.s = s
+
+ -- Gives us the buffer size to allocate
+ local der_len = _C.i2d_ECDSA_SIG(ecdsa, nil)
+
+ local der_sig_ptr = ffi_new("unsigned char *[1]")
+ local der_sig_bin = ffi_new("unsigned char [?]", der_len)
+ der_sig_ptr[0] = der_sig_bin
+ der_len = _C.i2d_ECDSA_SIG(ecdsa, der_sig_ptr)
+
+ local der_str = ffi_string(der_sig_bin, der_len)
+ return der_str, nil
+end
+
+
+local Cert = {}
+_M.Cert = Cert
+
+
+--- Create a new Certificate object
+-- @param payload A PEM or DER format X509 certificate
+-- @returns Cert, error_string
+function Cert.new(self, payload)
+ if not payload then
+ return nil, "Must pass a PEM or binary DER cert"
+ end
+ local bio = _C.BIO_new(_C.BIO_s_mem())
+ ffi_gc(bio, _C.BIO_vfree)
+ local x509
+ if payload:find('-----BEGIN') then
+ if _C.BIO_puts(bio, payload) < 0 then
+ return _err()
+ end
+ x509 = _C.PEM_read_bio_X509(bio, nil, nil, nil)
+ else
+ if _C.BIO_write(bio, payload, #payload) < 0 then
+ return _err()
+ end
+ x509 = _C.d2i_X509_bio(bio, nil)
+ end
+ if x509 == nil then
+ return _err()
+ end
+ ffi_gc(x509, _C.X509_free)
+ self.x509 = x509
+ local public_key, err = self:get_public_key()
+ if not public_key then
+ return nil, err
+ end
+
+ ffi_gc(public_key, _C.EVP_PKEY_free)
+
+ self.public_key = public_key
+ return self, nil
+end
+
+
+--- Retrieve the DER format of the certificate
+-- @returns Binary DER format, error_string
+function Cert.get_der(self)
+ local bufp = ffi_new("unsigned char *[1]")
+ local len = _C.i2d_X509(self.x509, bufp)
+ if len < 0 then
+ return _err()
+ end
+ local der = ffi_string(bufp[0], len)
+ return der, nil
+end
+
+--- Retrieve the cert fingerprint
+-- @param digest_name the Type of digest to use (e.g., "SHA256")
+-- @returns fingerprint_string, error_string
+function Cert.get_fingerprint(self, digest_name)
+ local md = _C.EVP_get_digestbyname(digest_name)
+ if md == nil then
+ return _err()
+ end
+ local buf = ffi_new("unsigned char[?]", 32)
+ local len = ffi_new("unsigned int[1]", 32)
+ if _C.X509_digest(self.x509, md, buf, len) ~= 1 then
+ return _err()
+ end
+ local raw = ffi_string(buf, len[0])
+ local t = {}
+ raw:gsub('.', function (c) table.insert(t, string.format('%02X', string.byte(c))) end)
+ return table.concat(t, ":"), nil
+end
+
+--- Retrieve the public key from the CERT
+-- @returns An OpenSSL EVP PKEY object representing the public key, error_string
+function Cert.get_public_key(self)
+ local evp_pkey = _C.X509_get_pubkey(self.x509)
+ if evp_pkey == nil then
+ return _err()
+ end
+
+ return evp_pkey, nil
+end
+
+--- Verify the Certificate is trusted
+-- @param trusted_cert_file File path to a list of PEM encoded trusted certificates
+-- @return bool, error_string
+function Cert.verify_trust(self, trusted_cert_file)
+ local store = _C.X509_STORE_new()
+ if store == nil then
+ return _err(false)
+ end
+ ffi_gc(store, _C.X509_STORE_free)
+ if _C.X509_STORE_load_locations(store, trusted_cert_file, nil) ~=1 then
+ return _err(false)
+ end
+
+ local ctx = _C.X509_STORE_CTX_new()
+ if store == nil then
+ return _err(false)
+ end
+ ffi_gc(ctx, _C.X509_STORE_CTX_free)
+ if _C.X509_STORE_CTX_init(ctx, store, self.x509, nil) ~= 1 then
+ return _err(false)
+ end
+
+ if _C.X509_verify_cert(ctx) ~= 1 then
+ local code = _C.X509_STORE_CTX_get_error(ctx)
+ local msg = ffi_string(_C.X509_verify_cert_error_string(code))
+ _C.X509_STORE_CTX_cleanup(ctx)
+ return false, msg
+ end
+ _C.X509_STORE_CTX_cleanup(ctx)
+ return true, nil
+
+end
+
+local PublicKey = {}
+_M.PublicKey = PublicKey
+
+--- Create a new PublicKey object
+--
+-- If a PEM fornatted key is provided, the key must start with
+--
+-- ----- BEGIN PUBLIC KEY -----
+--
+-- @param payload A PEM or DER format public key file
+-- @return PublicKey, error_string
+function PublicKey.new(self, payload)
+ if not payload then
+ return nil, "Must pass a PEM or binary DER public key"
+ end
+ local bio = _C.BIO_new(_C.BIO_s_mem())
+ ffi_gc(bio, _C.BIO_vfree)
+ local pkey
+ if payload:find('-----BEGIN') then
+ if _C.BIO_puts(bio, payload) < 0 then
+ return _err()
+ end
+ pkey = _C.PEM_read_bio_PUBKEY(bio, nil, nil, nil)
+ else
+ if _C.BIO_write(bio, payload, #payload) < 0 then
+ return _err()
+ end
+ pkey = _C.d2i_PUBKEY_bio(bio, nil)
+ end
+ if pkey == nil then
+ return _err()
+ end
+ ffi_gc(pkey, _C.EVP_PKEY_free)
+ self.public_key = pkey
+ return self, nil
+end
+
+local RSAEncryptor= {}
+_M.RSAEncryptor = RSAEncryptor
+
+--- Create a new RSAEncryptor
+-- @param key_source An instance of Cert or PublicKey used for verification
+-- @param padding padding type to use
+-- @param digest_alg digest algorithm to use
+-- @returns RSAEncryptor, err_string
+function RSAEncryptor.new(self, key_source, padding, digest_alg)
+ if not key_source then
+ return nil, "You must pass in an key_source for a public key"
+ end
+ local evp_public_key = key_source.public_key
+ self.evp_pkey = evp_public_key
+ self.padding = padding or CONST.RSA_PKCS1_OAEP_PADDING
+ self.digest_alg = digest_alg or CONST.SHA256_DIGEST
+ return self, nil
+end
+
+
+
+--- Encrypts the payload
+-- @param payload plain text payload
+-- @returns encrypted payload, error_string
+function RSAEncryptor.encrypt(self, payload)
+
+ local ctx, err_str = _create_evp_ctx(self, true)
+
+ if not ctx then
+ return nil, err_str
+ end
+ local len = ffi_new("size_t [1]")
+ if _C.EVP_PKEY_encrypt(ctx, nil, len, payload, #payload) <= 0 then
+ return _err()
+ end
+ local buf = ffi_new("unsigned char[?]", len[0])
+ if _C.EVP_PKEY_encrypt(ctx, buf, len, payload, #payload) <= 0 then
+ return _err()
+ end
+
+ return ffi_string(buf, len[0])
+
+end
+
+
+local RSADecryptor= {algo="RSA"}
+_M.RSADecryptor = RSADecryptor
+
+--- Create a new RSADecryptor
+-- @param pem_private_key A private key string in PEM format
+-- @param password password for the private key (if required)
+-- @param padding padding type to use
+-- @param digest_alg digest algorithm to use
+-- @returns RSADecryptor, error_string
+function RSADecryptor.new(self, pem_private_key, password, padding, digest_alg)
+ self.padding = padding or CONST.RSA_PKCS1_OAEP_PADDING
+ self.digest_alg = digest_alg or CONST.SHA256_DIGEST
+ return _new_key (
+ self,
+ {
+ pem_private_key = pem_private_key,
+ password = password
+ }
+ )
+end
+
+--- Decrypts the cypher text
+-- @param cypher_text encrypted payload
+-- @param padding rsa pading mode to use, Defaults to RSA_PKCS1_PADDING
+function RSADecryptor.decrypt(self, cypher_text)
+
+ local ctx, err_code, err_str = _create_evp_ctx(self, false)
+
+ if not ctx then
+ return nil, err_code, err_str
+ end
+
+ local len = ffi_new("size_t [1]")
+ if _C.EVP_PKEY_decrypt(ctx, nil, len, cypher_text, #cypher_text) <= 0 then
+ return _err()
+ end
+
+ local buf = ffi_new("unsigned char[?]", len[0])
+ if _C.EVP_PKEY_decrypt(ctx, buf, len, cypher_text, #cypher_text) <= 0 then
+ return _err()
+ end
+
+ return ffi_string(buf, len[0])
+
+end
+
+return _M