diff options
Diffstat (limited to 'src/luasec/x509.c')
-rw-r--r-- | src/luasec/x509.c | 748 |
1 files changed, 748 insertions, 0 deletions
diff --git a/src/luasec/x509.c b/src/luasec/x509.c new file mode 100644 index 0000000..84f22ee --- /dev/null +++ b/src/luasec/x509.c @@ -0,0 +1,748 @@ +/*-------------------------------------------------------------------------- + * LuaSec 1.0.2 + * + * Copyright (C) 2014-2021 Kim Alvefur, Paul Aurich, Tobias Markmann + * Matthew Wild, Bruno Silvestre. + * + *--------------------------------------------------------------------------*/ + +#include <stdio.h> +#include <string.h> + +#if defined(WIN32) +#include <ws2tcpip.h> +#include <windows.h> +#else +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif + +#include <openssl/ssl.h> +#include <openssl/x509v3.h> +#include <openssl/evp.h> +#include <openssl/err.h> +#include <openssl/asn1.h> +#include <openssl/bio.h> +#include <openssl/bn.h> + +#include "../lua.h" +#include "../lauxlib.h" + +#include "x509.h" + + +#ifndef LSEC_API_OPENSSL_1_1_0 +#define X509_get0_notBefore X509_get_notBefore +#define X509_get0_notAfter X509_get_notAfter +#define ASN1_STRING_get0_data ASN1_STRING_data +#endif + +static const char* hex_tab = "0123456789abcdef"; + +/** + * Push the certificate on the stack. + */ +void lsec_pushx509(lua_State* L, X509 *cert) +{ + p_x509 cert_obj = (p_x509)lua_newuserdata(L, sizeof(t_x509)); + cert_obj->cert = cert; + cert_obj->encode = LSEC_AI5_STRING; + luaL_getmetatable(L, "SSL:Certificate"); + lua_setmetatable(L, -2); +} + +/** + * Return the OpenSSL certificate X509. + */ +X509* lsec_checkx509(lua_State* L, int idx) +{ + return ((p_x509)luaL_checkudata(L, idx, "SSL:Certificate"))->cert; +} + +/** + * Return LuaSec certificate X509 representation. + */ +p_x509 lsec_checkp_x509(lua_State* L, int idx) +{ + return (p_x509)luaL_checkudata(L, idx, "SSL:Certificate"); +} + +/*---------------------------------------------------------------------------*/ + +#if defined(LUASEC_INET_NTOP) +/* + * For WinXP (SP3), set the following preprocessor macros: + * LUASEC_INET_NTOP + * WINVER=0x0501 + * _WIN32_WINNT=0x0501 + * NTDDI_VERSION=0x05010300 + * + * For IPv6 addresses, you need to add IPv6 Protocol to your interface. + * + */ +static const char *inet_ntop(int af, const char *src, char *dst, socklen_t size) +{ + int addrsize; + struct sockaddr *addr; + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + + switch (af) { + case AF_INET: + memset((void*)&addr4, 0, sizeof(addr4)); + addr4.sin_family = AF_INET; + memcpy((void*)&addr4.sin_addr, src, sizeof(struct in_addr)); + addr = (struct sockaddr*)&addr4; + addrsize = sizeof(struct sockaddr_in); + break; + case AF_INET6: + memset((void*)&addr6, 0, sizeof(addr6)); + addr6.sin6_family = AF_INET6; + memcpy((void*)&addr6.sin6_addr, src, sizeof(struct in6_addr)); + addr = (struct sockaddr*)&addr6; + addrsize = sizeof(struct sockaddr_in6); + break; + default: + return NULL; + } + + if(getnameinfo(addr, addrsize, dst, size, NULL, 0, NI_NUMERICHOST) != 0) + return NULL; + return dst; +} +#endif + +/*---------------------------------------------------------------------------*/ + +/** + * Convert the buffer 'in' to hexadecimal. + */ +static void to_hex(const char* in, int length, char* out) +{ + int i; + for (i = 0; i < length; i++) { + out[i*2] = hex_tab[(in[i] >> 4) & 0xF]; + out[i*2+1] = hex_tab[(in[i]) & 0xF]; + } +} + +/** + * Converts the ASN1_OBJECT into a textual representation and put it + * on the Lua stack. + */ +static void push_asn1_objname(lua_State* L, ASN1_OBJECT *object, int no_name) +{ + char buffer[256]; + int len = OBJ_obj2txt(buffer, sizeof(buffer), object, no_name); + len = (len < sizeof(buffer)) ? len : sizeof(buffer); + lua_pushlstring(L, buffer, len); +} + +/** + * Push the ASN1 string on the stack. + */ +static void push_asn1_string(lua_State* L, ASN1_STRING *string, int encode) +{ + int len; + unsigned char *data; + if (!string) { + lua_pushnil(L); + return; + } + switch (encode) { + case LSEC_AI5_STRING: + lua_pushlstring(L, (char*)ASN1_STRING_get0_data(string), ASN1_STRING_length(string)); + break; + case LSEC_UTF8_STRING: + len = ASN1_STRING_to_UTF8(&data, string); + if (len >= 0) { + lua_pushlstring(L, (char*)data, len); + OPENSSL_free(data); + } + else + lua_pushnil(L); + } +} + +/** + * Return a human readable time. + */ +static int push_asn1_time(lua_State *L, const ASN1_UTCTIME *tm) +{ + char *tmp; + long size; + BIO *out = BIO_new(BIO_s_mem()); + ASN1_TIME_print(out, tm); + size = BIO_get_mem_data(out, &tmp); + lua_pushlstring(L, tmp, size); + BIO_free(out); + return 1; +} + +/** + * Return a human readable IP address. + */ +static void push_asn1_ip(lua_State *L, ASN1_STRING *string) +{ + int af; + char dst[INET6_ADDRSTRLEN]; + unsigned char *ip = (unsigned char*)ASN1_STRING_get0_data(string); + switch(ASN1_STRING_length(string)) { + case 4: + af = AF_INET; + break; + case 16: + af = AF_INET6; + break; + default: + lua_pushnil(L); + return; + } + if(inet_ntop(af, ip, dst, INET6_ADDRSTRLEN)) + lua_pushstring(L, dst); + else + lua_pushnil(L); +} + +/** + * + */ +static int push_subtable(lua_State* L, int idx) +{ + lua_pushvalue(L, -1); + lua_gettable(L, idx-1); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + lua_newtable(L); + lua_pushvalue(L, -2); + lua_pushvalue(L, -2); + lua_settable(L, idx-3); + lua_replace(L, -2); /* Replace key with table */ + return 1; + } + lua_replace(L, -2); /* Replace key with table */ + return 0; +} + +/** + * Retrieve the general names from the object. + */ +static int push_x509_name(lua_State* L, X509_NAME *name, int encode) +{ + int i; + int n_entries; + ASN1_OBJECT *object; + X509_NAME_ENTRY *entry; + lua_newtable(L); + n_entries = X509_NAME_entry_count(name); + for (i = 0; i < n_entries; i++) { + entry = X509_NAME_get_entry(name, i); + object = X509_NAME_ENTRY_get_object(entry); + lua_newtable(L); + push_asn1_objname(L, object, 1); + lua_setfield(L, -2, "oid"); + push_asn1_objname(L, object, 0); + lua_setfield(L, -2, "name"); + push_asn1_string(L, X509_NAME_ENTRY_get_data(entry), encode); + lua_setfield(L, -2, "value"); + lua_rawseti(L, -2, i+1); + } + return 1; +} + +/*---------------------------------------------------------------------------*/ + +/** + * Retrieve the Subject from the certificate. + */ +static int meth_subject(lua_State* L) +{ + p_x509 px = lsec_checkp_x509(L, 1); + return push_x509_name(L, X509_get_subject_name(px->cert), px->encode); +} + +/** + * Retrieve the Issuer from the certificate. + */ +static int meth_issuer(lua_State* L) +{ + p_x509 px = lsec_checkp_x509(L, 1); + return push_x509_name(L, X509_get_issuer_name(px->cert), px->encode); +} + +/** + * Retrieve the extensions from the certificate. + */ +int meth_extensions(lua_State* L) +{ + int j; + int i = -1; + int n_general_names; + OTHERNAME *otherName; + X509_EXTENSION *extension; + GENERAL_NAME *general_name; + STACK_OF(GENERAL_NAME) *values; + p_x509 px = lsec_checkp_x509(L, 1); + X509 *peer = px->cert; + + /* Return (ret) */ + lua_newtable(L); + + while ((i = X509_get_ext_by_NID(peer, NID_subject_alt_name, i)) != -1) { + extension = X509_get_ext(peer, i); + if (extension == NULL) + break; + values = X509V3_EXT_d2i(extension); + if (values == NULL) + break; + + /* Push ret[oid] */ + push_asn1_objname(L, X509_EXTENSION_get_object(extension), 1); + push_subtable(L, -2); + + /* Set ret[oid].name = name */ + push_asn1_objname(L, X509_EXTENSION_get_object(extension), 0); + lua_setfield(L, -2, "name"); + + n_general_names = sk_GENERAL_NAME_num(values); + for (j = 0; j < n_general_names; j++) { + general_name = sk_GENERAL_NAME_value(values, j); + switch (general_name->type) { + case GEN_OTHERNAME: + otherName = general_name->d.otherName; + push_asn1_objname(L, otherName->type_id, 1); + if (push_subtable(L, -2)) { + push_asn1_objname(L, otherName->type_id, 0); + lua_setfield(L, -2, "name"); + } + push_asn1_string(L, otherName->value->value.asn1_string, px->encode); + lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); + lua_pop(L, 1); + break; + case GEN_DNS: + lua_pushstring(L, "dNSName"); + push_subtable(L, -2); + push_asn1_string(L, general_name->d.dNSName, px->encode); + lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); + lua_pop(L, 1); + break; + case GEN_EMAIL: + lua_pushstring(L, "rfc822Name"); + push_subtable(L, -2); + push_asn1_string(L, general_name->d.rfc822Name, px->encode); + lua_rawseti(L, -2, lua_rawlen(L, -2) + 1); + lua_pop(L, 1); + break; + case GEN_URI: + lua_pushstring(L, "uniformResourceIdentifier"); + push_subtable(L, -2); + push_asn1_string(L, general_name->d.uniformResourceIdentifier, px->encode); + lua_rawseti(L, -2, lua_rawlen(L, -2)+1); + lua_pop(L, 1); + break; + case GEN_IPADD: + lua_pushstring(L, "iPAddress"); + push_subtable(L, -2); + push_asn1_ip(L, general_name->d.iPAddress); + lua_rawseti(L, -2, lua_rawlen(L, -2)+1); + lua_pop(L, 1); + break; + case GEN_X400: + /* x400Address */ + /* not supported */ + break; + case GEN_DIRNAME: + /* directoryName */ + /* not supported */ + break; + case GEN_EDIPARTY: + /* ediPartyName */ + /* not supported */ + break; + case GEN_RID: + /* registeredID */ + /* not supported */ + break; + } + GENERAL_NAME_free(general_name); + } + sk_GENERAL_NAME_free(values); + lua_pop(L, 1); /* ret[oid] */ + i++; /* Next extension */ + } + return 1; +} + +/** + * Convert the certificate to PEM format. + */ +static int meth_pem(lua_State* L) +{ + char* data; + long bytes; + X509* cert = lsec_checkx509(L, 1); + BIO *bio = BIO_new(BIO_s_mem()); + if (!PEM_write_bio_X509(bio, cert)) { + lua_pushnil(L); + return 1; + } + bytes = BIO_get_mem_data(bio, &data); + if (bytes > 0) + lua_pushlstring(L, data, bytes); + else + lua_pushnil(L); + BIO_free(bio); + return 1; +} + +/** + * Extract public key in PEM format. + */ +static int meth_pubkey(lua_State* L) +{ + char* data; + long bytes; + int ret = 1; + X509* cert = lsec_checkx509(L, 1); + BIO *bio = BIO_new(BIO_s_mem()); + EVP_PKEY *pkey = X509_get_pubkey(cert); + if(PEM_write_bio_PUBKEY(bio, pkey)) { + bytes = BIO_get_mem_data(bio, &data); + if (bytes > 0) { + lua_pushlstring(L, data, bytes); + switch(EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: + lua_pushstring(L, "RSA"); + break; + case EVP_PKEY_DSA: + lua_pushstring(L, "DSA"); + break; + case EVP_PKEY_DH: + lua_pushstring(L, "DH"); + break; + case EVP_PKEY_EC: + lua_pushstring(L, "EC"); + break; + default: + lua_pushstring(L, "Unknown"); + break; + } + lua_pushinteger(L, EVP_PKEY_bits(pkey)); + ret = 3; + } + else + lua_pushnil(L); + } + else + lua_pushnil(L); + /* Cleanup */ + BIO_free(bio); + EVP_PKEY_free(pkey); + return ret; +} + +/** + * Compute the fingerprint. + */ +static int meth_digest(lua_State* L) +{ + unsigned int bytes; + const EVP_MD *digest = NULL; + unsigned char buffer[EVP_MAX_MD_SIZE]; + char hex_buffer[EVP_MAX_MD_SIZE*2]; + X509 *cert = lsec_checkx509(L, 1); + const char *str = luaL_optstring(L, 2, NULL); + if (!str) + digest = EVP_sha1(); + else { + if (!strcmp(str, "sha1")) + digest = EVP_sha1(); + else if (!strcmp(str, "sha256")) + digest = EVP_sha256(); + else if (!strcmp(str, "sha512")) + digest = EVP_sha512(); + } + if (!digest) { + lua_pushnil(L); + lua_pushfstring(L, "digest algorithm not supported (%s)", str); + return 2; + } + if (!X509_digest(cert, digest, buffer, &bytes)) { + lua_pushnil(L); + lua_pushfstring(L, "error processing the certificate (%s)", + ERR_reason_error_string(ERR_get_error())); + return 2; + } + to_hex((char*)buffer, bytes, hex_buffer); + lua_pushlstring(L, hex_buffer, bytes*2); + return 1; +} + +/** + * Check if the certificate is valid in a given time. + */ +static int meth_valid_at(lua_State* L) +{ + int nb, na; + X509* cert = lsec_checkx509(L, 1); + time_t time = luaL_checkinteger(L, 2); + nb = X509_cmp_time(X509_get0_notBefore(cert), &time); + time -= 1; + na = X509_cmp_time(X509_get0_notAfter(cert), &time); + lua_pushboolean(L, nb == -1 && na == 1); + return 1; +} + +/** + * Return the serial number. + */ +static int meth_serial(lua_State *L) +{ + char *tmp; + BIGNUM *bn; + ASN1_INTEGER *serial; + X509* cert = lsec_checkx509(L, 1); + serial = X509_get_serialNumber(cert); + bn = ASN1_INTEGER_to_BN(serial, NULL); + tmp = BN_bn2hex(bn); + lua_pushstring(L, tmp); + BN_free(bn); + OPENSSL_free(tmp); + return 1; +} + +/** + * Return not before date. + */ +static int meth_notbefore(lua_State *L) +{ + X509* cert = lsec_checkx509(L, 1); + return push_asn1_time(L, X509_get0_notBefore(cert)); +} + +/** + * Return not after date. + */ +static int meth_notafter(lua_State *L) +{ + X509* cert = lsec_checkx509(L, 1); + return push_asn1_time(L, X509_get0_notAfter(cert)); +} + +/** + * Check if this certificate issued some other certificate + */ +static int meth_issued(lua_State* L) +{ + int ret, i, len; + + X509_STORE_CTX* ctx = NULL; + X509_STORE* root = NULL; + STACK_OF(X509)* chain = NULL; + + X509* issuer = lsec_checkx509(L, 1); + X509* subject = lsec_checkx509(L, 2); + X509* cert = NULL; + + len = lua_gettop(L); + + /* Check that all arguments are certificates */ + + for (i = 3; i <= len; i++) { + lsec_checkx509(L, i); + } + + /* Before allocating things that require freeing afterwards */ + + chain = sk_X509_new_null(); + ctx = X509_STORE_CTX_new(); + root = X509_STORE_new(); + + if (ctx == NULL || root == NULL) { + lua_pushnil(L); + lua_pushstring(L, "X509_STORE_new() or X509_STORE_CTX_new() error"); + ret = 2; + goto cleanup; + } + + ret = X509_STORE_add_cert(root, issuer); + + if(!ret) { + lua_pushnil(L); + lua_pushstring(L, "X509_STORE_add_cert() error"); + ret = 2; + goto cleanup; + } + + for (i = 3; i <= len && lua_isuserdata(L, i); i++) { + cert = lsec_checkx509(L, i); + sk_X509_push(chain, cert); + } + + ret = X509_STORE_CTX_init(ctx, root, subject, chain); + + if(!ret) { + lua_pushnil(L); + lua_pushstring(L, "X509_STORE_CTX_init() error"); + ret = 2; + goto cleanup; + } + + /* Actual verification */ + if (X509_verify_cert(ctx) <= 0) { + ret = X509_STORE_CTX_get_error(ctx); + lua_pushnil(L); + lua_pushstring(L, X509_verify_cert_error_string(ret)); + ret = 2; + } else { + lua_pushboolean(L, 1); + ret = 1; + } + +cleanup: + + if (ctx != NULL) { + X509_STORE_CTX_free(ctx); + } + + if (chain != NULL) { + X509_STORE_free(root); + } + + sk_X509_free(chain); + + return ret; +} + +/** + * Collect X509 objects. + */ +static int meth_destroy(lua_State* L) +{ + p_x509 px = lsec_checkp_x509(L, 1); + if (px->cert) { + X509_free(px->cert); + px->cert = NULL; + } + return 0; +} + +static int meth_tostring(lua_State *L) +{ + X509* cert = lsec_checkx509(L, 1); + lua_pushfstring(L, "X509 certificate: %p", cert); + return 1; +} + +/** + * Set the encode for ASN.1 string. + */ +static int meth_set_encode(lua_State* L) +{ + int succ = 0; + p_x509 px = lsec_checkp_x509(L, 1); + const char *enc = luaL_checkstring(L, 2); + if (strncmp(enc, "ai5", 3) == 0) { + succ = 1; + px->encode = LSEC_AI5_STRING; + } else if (strncmp(enc, "utf8", 4) == 0) { + succ = 1; + px->encode = LSEC_UTF8_STRING; + } + lua_pushboolean(L, succ); + return 1; +} + +/** + * Get signature name. + */ +static int meth_get_signature_name(lua_State* L) +{ + p_x509 px = lsec_checkp_x509(L, 1); + int nid = X509_get_signature_nid(px->cert); + const char *name = OBJ_nid2sn(nid); + if (!name) + lua_pushnil(L); + else + lua_pushstring(L, name); + return 1; +} + +/*---------------------------------------------------------------------------*/ + +static int load_cert(lua_State* L) +{ + X509 *cert; + size_t bytes; + const char* data; + BIO *bio = BIO_new(BIO_s_mem()); + data = luaL_checklstring(L, 1, &bytes); + BIO_write(bio, data, bytes); + cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (cert) + lsec_pushx509(L, cert); + else + lua_pushnil(L); + BIO_free(bio); + return 1; +} + +/*---------------------------------------------------------------------------*/ + +/** + * Certificate methods. + */ +static luaL_Reg methods[] = { + {"digest", meth_digest}, + {"setencode", meth_set_encode}, + {"extensions", meth_extensions}, + {"getsignaturename", meth_get_signature_name}, + {"issuer", meth_issuer}, + {"notbefore", meth_notbefore}, + {"notafter", meth_notafter}, + {"issued", meth_issued}, + {"pem", meth_pem}, + {"pubkey", meth_pubkey}, + {"serial", meth_serial}, + {"subject", meth_subject}, + {"validat", meth_valid_at}, + {NULL, NULL} +}; + +/** + * X509 metamethods. + */ +static luaL_Reg meta[] = { + {"__close", meth_destroy}, + {"__gc", meth_destroy}, + {"__tostring", meth_tostring}, + {NULL, NULL} +}; + +/** + * X509 functions. + */ +static luaL_Reg funcs[] = { + {"load", load_cert}, + {NULL, NULL} +}; + +/*--------------------------------------------------------------------------*/ + +LSEC_API int luaopen_ssl_x509(lua_State *L) +{ + /* Register the functions and tables */ + luaL_newmetatable(L, "SSL:Certificate"); + setfuncs(L, meta); + + luaL_newlib(L, methods); + lua_setfield(L, -2, "__index"); + + luaL_newlib(L, funcs); + lua_pushvalue(L, -1); + lua_setglobal(L, "x509"); + + return 1; +} |