GithubHelp home page GithubHelp logo

lua-resty-jwt's People

Contributors

deadleg avatar extremesurfer avatar fermaem avatar fraburnham avatar kleht8 avatar leyan-pang avatar lucafavatella avatar mrrobby avatar ravenscar avatar rohitjoshi avatar saks avatar skylothar avatar toonetown avatar wuthefwasthat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lua-resty-jwt's Issues

Memory leak jwt:verify with X509 certificate

I'm experiencing memory leak when using X509. Ran through Valgrind and most of the leak points to X509_get_pubkey. Not sure if this a OpenSSL issue or something else.

Specs:
Centos 7 - 64Bit
nginx version: openresty/1.11.2.3
openssl-1.0.1e-60.el7_3.1.x86_64
SkyLothar/lua-resty-jwt 0.1.10

valgrind -v --log-file=memcheck.log --tool=memcheck --leak-check=full openresty-valgrind -c /srv/openresty-apigw/conf/nginx.conf -p /srv/openresty-apigw

memcheck.log

==16375== Memcheck, a memory error detector ==16375== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. ==16375== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info ==16375== Command: openresty-valgrind -c /srv/openresty-apigw/conf/nginx.conf -p /srv/openresty-apigw ==16375== Parent PID: 16374 ==16375== --16375-- --16375-- Valgrind options: --16375-- -v --16375-- --log-file=memcheck.log --16375-- --tool=memcheck --16375-- --leak-check=full --16375-- Contents of /proc/version: --16375-- Linux version 3.10.0-514.16.1.el7.x86_64 ([email protected]) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC) ) #1 SMP Wed Apr 12 15:04:24 UTC 2017 --16375-- --16375-- Arch and hwcaps: AMD64, LittleEndian, amd64-cx16-rdtscp-sse3-avx --16375-- Page sizes: currently 4096, max supported 4096 --16375-- Valgrind library directory: /usr/lib64/valgrind --16375-- Reading syms from /usr/local/openresty-valgrind/nginx/sbin/nginx --16375-- object doesn't have a symbol table --16375-- Reading syms from /usr/lib64/ld-2.17.so --16375-- Reading syms from /usr/lib64/valgrind/memcheck-amd64-linux --16375-- object doesn't have a symbol table --16375-- object doesn't have a dynamic symbol table --16375-- Scheduler: using generic scheduler lock implementation. --16375-- Reading suppressions file: /usr/lib64/valgrind/default.supp ==16375== embedded gdbserver: reading from /tmp/vgdb-pipe-from-vgdb-to-16375-by-root-on-vm1 ==16375== embedded gdbserver: writing to /tmp/vgdb-pipe-to-vgdb-from-16375-by-root-on-vm1 ==16375== embedded gdbserver: shared mem /tmp/vgdb-pipe-shared-mem-vgdb-16375-by-root-on-vm1 ==16375== ==16375== TO CONTROL THIS PROCESS USING vgdb (which you probably ==16375== don't want to do, unless you know exactly what you're doing, ==16375== or are doing some strange experiment): ==16375== /usr/lib64/valgrind/../../bin/vgdb --pid=16375 ...command... ==16375== ==16375== TO DEBUG THIS PROCESS USING GDB: start GDB like this ==16375== /path/to/gdb openresty-valgrind ==16375== and then give GDB the following command ==16375== target remote | /usr/lib64/valgrind/../../bin/vgdb --pid=16375 ==16375== --pid is optional if only one valgrind process is running ==16375== --16375-- REDIR: 0x4018e90 (ld-linux-x86-64.so.2:strlen) redirected to 0x38056d91 (???) --16375-- REDIR: 0x4018c60 (ld-linux-x86-64.so.2:index) redirected to 0x38056dab (???) --16375-- Reading syms from /usr/lib64/valgrind/vgpreload_core-amd64-linux.so --16375-- Reading syms from /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so ==16375== WARNING: new redirection conflicts with existing -- ignoring it --16375-- old: 0x04018e90 (strlen ) R-> (0000.0) 0x38056d91 ??? --16375-- new: 0x04018e90 (strlen ) R-> (2007.0) 0x04c2aa90 strlen --16375-- REDIR: 0x4018e10 (ld-linux-x86-64.so.2:strcmp) redirected to 0x4c2bbe0 (strcmp) --16375-- REDIR: 0x4019a00 (ld-linux-x86-64.so.2:mempcpy) redirected to 0x4c2ec20 (mempcpy) --16375-- Reading syms from /usr/lib64/libdl-2.17.so --16375-- Reading syms from /usr/lib64/libpthread-2.17.so --16375-- Reading syms from /usr/lib64/libcrypt-2.17.so --16375-- Reading syms from /usr/local/openresty-valgrind/luajit/lib/libluajit-5.1.so.2.1.0 --16375-- object doesn't have a symbol table --16375-- Reading syms from /usr/lib64/libm-2.17.so --16375-- Reading syms from /usr/local/openresty/pcre/lib/libpcre.so.1.2.8 --16375-- object doesn't have a symbol table --16375-- Reading syms from /usr/local/openresty-debug/openssl/lib/libssl.so.1.0.0 --16375-- object doesn't have a symbol table --16375-- Reading syms from /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0 --16375-- object doesn't have a symbol table --16375-- Reading syms from /usr/local/openresty/zlib/lib/libz.so.1.2.11 --16375-- object doesn't have a symbol table --16375-- Reading syms from /usr/lib64/libGeoIP.so.1.5.0 --16375-- object doesn't have a symbol table --16375-- Reading syms from /usr/lib64/libc-2.17.so --16375-- Reading syms from /usr/lib64/libfreebl3.so --16375-- object doesn't have a symbol table --16375-- Reading syms from /usr/lib64/libgcc_s-4.8.5-20150702.so.1 --16375-- object doesn't have a symbol table --16375-- REDIR: 0x6884ec0 (libc.so.6:strcasecmp) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x6881c40 (libc.so.6:strnlen) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x6887190 (libc.so.6:strncasecmp) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x68846a0 (libc.so.6:memset) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x6884650 (libc.so.6:memcpy@GLIBC_2.2.5) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x68800c0 (libc.so.6:strcmp) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x6881b10 (libc.so.6:strlen) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x6889860 (libc.so.6:memcpy@@GLIBC_2.14) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x6881b60 (libc.so.6:__GI_strlen) redirected to 0x4c2a9f0 (__GI_strlen) --16375-- REDIR: 0x6883630 (libc.so.6:__GI_strrchr) redirected to 0x4c2a450 (__GI_strrchr) --16375-- REDIR: 0xffffffffff600400 (???:???) redirected to 0x38056d7d (???) --16375-- REDIR: 0x687af70 (libc.so.6:malloc) redirected to 0x4c27b5c (malloc) --16375-- REDIR: 0x68898d0 (libc.so.6:__GI_memcpy) redirected to 0x4c2c580 (__GI_memcpy) --16375-- REDIR: 0x6881d60 (libc.so.6:__GI_strncmp) redirected to 0x4c2b220 (__GI_strncmp) --16375-- REDIR: 0x6884d60 (libc.so.6:__GI_stpcpy) redirected to 0x4c2d8d0 (__GI_stpcpy) --16375-- REDIR: 0x6884870 (libc.so.6:__GI_mempcpy) redirected to 0x4c2e950 (__GI_mempcpy) --16375-- REDIR: 0x695ee60 (libc.so.6:__strlen_sse2_pminub) redirected to 0x4c2a9d0 (strlen) --16375-- REDIR: 0x6944220 (libc.so.6:__memcpy_ssse3_back) redirected to 0x4c2bfa0 (memcpy@@GLIBC_2.14) --16375-- REDIR: 0xffffffffff600000 (???:???) redirected to 0x38056d73 (???) --16375-- REDIR: 0x687b370 (libc.so.6:free) redirected to 0x4c28c56 (free) --16375-- REDIR: 0x689a540 (libc.so.6:__GI_strstr) redirected to 0x4c2eeb0 (__strstr_sse2) --16375-- REDIR: 0x68840c0 (libc.so.6:__GI_memcmp) redirected to 0x4c2d4d0 (__GI_memcmp) --16375-- REDIR: 0x6884700 (libc.so.6:__GI_memset) redirected to 0x4c2de80 (memset) --16375-- REDIR: 0x6884080 (libc.so.6:bcmp) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x6959790 (libc.so.6:__memcmp_sse4_1) redirected to 0x4c2d610 (__memcmp_sse4_1) --16375-- REDIR: 0x6883d30 (libc.so.6:memchr) redirected to 0x4c2bc80 (memchr) --16375-- REDIR: 0x692e570 (libc.so.6:__strcmp_sse42) redirected to 0x4c2bb90 (__strcmp_sse42) --16375-- REDIR: 0x687b450 (libc.so.6:realloc) redirected to 0x4c29aae (realloc) --16375-- REDIR: 0x6937f80 (libc.so.6:__strncasecmp_avx) redirected to 0x4c2b450 (strncasecmp) --16375-- REDIR: 0x68835b0 (libc.so.6:strncpy) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x688f930 (libc.so.6:__strncpy_sse2_unaligned) redirected to 0x4c2b060 (__strncpy_sse2_unaligned) --16375-- REDIR: 0x68844c0 (libc.so.6:__GI_memmove) redirected to 0x4c2e190 (__GI_memmove) --16375-- REDIR: 0x6880040 (libc.so.6:__GI_strchr) redirected to 0x4c2a580 (__GI_strchr) --16375-- REDIR: 0x688b150 (libc.so.6:strchrnul) redirected to 0x4c2e740 (strchrnul) --16375-- REDIR: 0x6880100 (libc.so.6:__GI_strcmp) redirected to 0x4c2baf0 (__GI_strcmp) --16375-- REDIR: 0x688efa0 (libc.so.6:__GI_strncpy) redirected to 0x4c2adc0 (__GI_strncpy) --16375-- REDIR: 0x687b960 (libc.so.6:calloc) redirected to 0x4c298df (calloc) --16375-- Reading syms from /usr/lib64/libnss_files-2.17.so --16375-- REDIR: 0x6881590 (libc.so.6:__GI_strcpy) redirected to 0x4c2ab90 (__GI_strcpy) --16375-- REDIR: 0x688af10 (libc.so.6:rawmemchr) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x688af40 (libc.so.6:__GI___rawmemchr) redirected to 0x4c2e7a0 (__GI___rawmemchr) --16375-- REDIR: 0x6936910 (libc.so.6:__strcasecmp_avx) redirected to 0x4c2b370 (strcasecmp) --16375-- REDIR: 0x6881d20 (libc.so.6:strncmp) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x692f320 (libc.so.6:__strncmp_sse42) redirected to 0x4c2b300 (__strncmp_sse42) --16375-- REDIR: 0x68836d0 (libc.so.6:strpbrk) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x69366d0 (libc.so.6:__strpbrk_sse42) redirected to 0x4c2efd0 (strpbrk) --16375-- REDIR: 0x6880000 (libc.so.6:index) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x692e4c0 (libc.so.6:__strchr_sse42) redirected to 0x4c2a640 (index) --16375-- REDIR: 0x69497c0 (libc.so.6:__memmove_ssse3_back) redirected to 0x4c2bd40 (memcpy@GLIBC_2.2.5) --16375-- REDIR: 0x689ab00 (libc.so.6:strstr) redirected to 0x4a227b0 (_vgnU_ifunc_wrapper) --16375-- REDIR: 0x69304a0 (libc.so.6:__strstr_sse42) redirected to 0x4c2ef40 (__strstr_sse42) ==16375== Syscall param epoll_ctl(event) points to uninitialised byte(s) ==16375== at 0x68F2CBA: epoll_ctl (in /usr/lib64/libc-2.17.so) ==16375== by 0x4605E5: ??? (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x460129: ??? (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x450A2B: ??? (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x45D431: ngx_single_process_cycle (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x41CA43: main (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== Address 0xfff0000a4 is on thread 1's stack ==16375== --16375-- memcheck GC: 1000 nodes, 998 survivors (99.8%) --16375-- memcheck GC: 1414 new table size (stepup) --16375-- memcheck GC: 1414 nodes, 1414 survivors (100.0%) --16375-- memcheck GC: 1999 new table size (stepup) --16375-- memcheck GC: 1999 nodes, 1999 survivors (100.0%) --16375-- memcheck GC: 2827 new table size (stepup) --16375-- REDIR: 0x4019b50 (ld-linux-x86-64.so.2:stpcpy) redirected to 0x4c2dc50 (stpcpy) --16375-- Reading syms from /usr/local/openresty-valgrind/lualib/cjson.so --16375-- object doesn't have a symbol table --16375-- Discarding syms at 0x7fea1e0-0x7fec70c in /usr/local/openresty-valgrind/lualib/cjson.so due to munmap() --16375-- Discarding syms at 0x73d71d0-0x73de3e4 in /usr/lib64/libnss_files-2.17.so due to munmap() ==16375== ==16375== HEAP SUMMARY: ==16375== in use at exit: 24,697,697 bytes in 155,572 blocks ==16375== total heap usage: 9,573,377 allocs, 9,417,805 frees, 937,530,735 bytes allocated ==16375== ==16375== Searching for pointers to 155,572 not-freed blocks ==16375== Checked 11,364,680 bytes ==16375== ==16375== 112 bytes in 2 blocks are possibly lost in loss record 70 of 156 ==16375== at 0x4C27BE3: malloc (vg_replace_malloc.c:299) ==16375== by 0x5FE8044: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x5FE86FB: CRYPTO_malloc (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60A3C8B: EVP_PKEY_new (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60B5F49: X509_PUBKEY_get (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60D8987: X509_get_pubkey (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x40417D2: ??? ==16375== by 0x7134D97: ??? ==16375== by 0x54941FA: ??? (in /usr/local/openresty-valgrind/luajit/lib/libluajit-5.1.so.2.1.0) ==16375== by 0x54BAAFA: lua_resume (in /usr/local/openresty-valgrind/luajit/lib/libluajit-5.1.so.2.1.0) ==16375== by 0x5623BF: ngx_http_lua_run_thread (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x56AA48: ??? (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== ==16375== 552 bytes in 23 blocks are possibly lost in loss record 96 of 156 ==16375== at 0x4C27BE3: malloc (vg_replace_malloc.c:299) ==16375== by 0x5FE8044: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x5FE86FB: CRYPTO_malloc (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x602F133: BN_new (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60B6AE6: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60B6C4A: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60C1027: asn1_ex_c2i (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60C0F78: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60BF665: ASN1_item_ex_d2i (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60C0A55: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60C062A: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60C00CC: ASN1_item_ex_d2i (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== ==16375== 1,664 bytes in 16 blocks are possibly lost in loss record 125 of 156 ==16375== at 0x4C27BE3: malloc (vg_replace_malloc.c:299) ==16375== by 0x5FE8044: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x5FE86FB: CRYPTO_malloc (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x604C319: BN_MONT_CTX_new (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x604C7D5: BN_MONT_CTX_set_locked (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x606A943: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x6073E93: RSA_public_decrypt (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x606CE4E: int_rsa_verify (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x606D26B: RSA_verify (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x6072CF4: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60AB529: EVP_PKEY_verify (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60ACC06: EVP_DigestVerifyFinal (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== ==16375== 1,848 bytes in 11 blocks are possibly lost in loss record 127 of 156 ==16375== at 0x4C27BE3: malloc (vg_replace_malloc.c:299) ==16375== by 0x5FE8044: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x5FE86FB: CRYPTO_malloc (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x606C0BA: RSA_new_method (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x606BFAE: RSA_new (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x606FE81: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60BD45B: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60BD1A7: ASN1_item_ex_new (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60BFE6B: ASN1_item_ex_d2i (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60BF43E: ASN1_item_d2i (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x6070088: d2i_RSAPublicKey (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60702F0: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== ==16375== 6,960 bytes in 38 blocks are possibly lost in loss record 141 of 156 ==16375== at 0x4C27BE3: malloc (vg_replace_malloc.c:299) ==16375== by 0x5FE8044: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x5FE86FB: CRYPTO_malloc (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x602F2FB: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x602F634: bn_expand2 (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x602FF9B: BN_bin2bn (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60B6C75: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60C1027: asn1_ex_c2i (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60C0F78: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60BF665: ASN1_item_ex_d2i (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60C0A55: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60C062A: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== ==16375== 7,168 bytes in 28 blocks are possibly lost in loss record 142 of 156 ==16375== at 0x4C27BE3: malloc (vg_replace_malloc.c:299) ==16375== by 0x5FE8044: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x5FE86FB: CRYPTO_malloc (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x602F2FB: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x602F634: bn_expand2 (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x602F8A1: BN_copy (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x604C493: BN_MONT_CTX_set (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x604C801: BN_MONT_CTX_set_locked (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x606A943: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x6073E93: RSA_public_decrypt (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x606CE4E: int_rsa_verify (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x606D26B: RSA_verify (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== ==16375== 22,360 bytes in 43 blocks are possibly lost in loss record 145 of 156 ==16375== at 0x4C27BE3: malloc (vg_replace_malloc.c:299) ==16375== by 0x5FE8044: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x5FE86FB: CRYPTO_malloc (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x602F2FB: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x602F634: bn_expand2 (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60305A7: BN_set_bit (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x604C64E: BN_MONT_CTX_set (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x604C801: BN_MONT_CTX_set_locked (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x606A943: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x6073E93: RSA_public_decrypt (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x606CE4E: int_rsa_verify (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x606D26B: RSA_verify (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== ==16375== 98,304 bytes in 1 blocks are possibly lost in loss record 146 of 156 ==16375== at 0x4C27BE3: malloc (vg_replace_malloc.c:299) ==16375== by 0x457883: ngx_alloc (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x450DC4: ??? (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x45D431: ngx_single_process_cycle (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x41CA43: main (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== ==16375== 98,304 bytes in 1 blocks are possibly lost in loss record 147 of 156 ==16375== at 0x4C27BE3: malloc (vg_replace_malloc.c:299) ==16375== by 0x457883: ngx_alloc (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x450EA1: ??? (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x45D431: ngx_single_process_cycle (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x41CA43: main (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== ==16375== 245,760 bytes in 1 blocks are possibly lost in loss record 148 of 156 ==16375== at 0x4C27BE3: malloc (vg_replace_malloc.c:299) ==16375== by 0x457883: ngx_alloc (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x450D54: ??? (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x45D431: ngx_single_process_cycle (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x41CA43: main (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== ==16375== 9,466,168 (372,792 direct, 9,093,376 indirect) bytes in 6,657 blocks are definitely lost in loss record 155 of 156 ==16375== at 0x4C27BE3: malloc (vg_replace_malloc.c:299) ==16375== by 0x5FE8044: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x5FE86FB: CRYPTO_malloc (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60A3C8B: EVP_PKEY_new (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60B5F49: X509_PUBKEY_get (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60D8987: X509_get_pubkey (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x54962EC: ??? (in /usr/local/openresty-valgrind/luajit/lib/libluajit-5.1.so.2.1.0) ==16375== by 0x553428D: ??? (in /usr/local/openresty-valgrind/luajit/lib/libluajit-5.1.so.2.1.0) ==16375== by 0x5559D39: ??? (in /usr/local/openresty-valgrind/luajit/lib/libluajit-5.1.so.2.1.0) ==16375== by 0x54941FA: ??? (in /usr/local/openresty-valgrind/luajit/lib/libluajit-5.1.so.2.1.0) ==16375== by 0x54BAAFA: lua_resume (in /usr/local/openresty-valgrind/luajit/lib/libluajit-5.1.so.2.1.0) ==16375== by 0x5623BF: ngx_http_lua_run_thread (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== ==16375== 14,644,616 (576,968 direct, 14,067,648 indirect) bytes in 10,303 blocks are definitely lost in loss record 156 of 156 ==16375== at 0x4C27BE3: malloc (vg_replace_malloc.c:299) ==16375== by 0x5FE8044: ??? (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x5FE86FB: CRYPTO_malloc (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60A3C8B: EVP_PKEY_new (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60B5F49: X509_PUBKEY_get (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x60D8987: X509_get_pubkey (in /usr/local/openresty-debug/openssl/lib/libcrypto.so.1.0.0) ==16375== by 0x40417D2: ??? ==16375== by 0x7134D97: ??? ==16375== by 0x54941FA: ??? (in /usr/local/openresty-valgrind/luajit/lib/libluajit-5.1.so.2.1.0) ==16375== by 0x54BAAFA: lua_resume (in /usr/local/openresty-valgrind/luajit/lib/libluajit-5.1.so.2.1.0) ==16375== by 0x5623BF: ngx_http_lua_run_thread (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x56AA48: ??? (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== ==16375== LEAK SUMMARY: ==16375== definitely lost: 949,760 bytes in 16,960 blocks ==16375== indirectly lost: 23,161,024 bytes in 135,524 blocks ==16375== possibly lost: 483,032 bytes in 164 blocks ==16375== still reachable: 103,881 bytes in 2,924 blocks ==16375== suppressed: 0 bytes in 0 blocks ==16375== Reachable blocks (those to which a pointer was found) are not shown. ==16375== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==16375== ==16375== Use --track-origins=yes to see where uninitialised values come from ==16375== ERROR SUMMARY: 13 errors from 13 contexts (suppressed: 0 from 0) ==16375== ==16375== 1 errors in context 1 of 13: ==16375== Syscall param epoll_ctl(event) points to uninitialised byte(s) ==16375== at 0x68F2CBA: epoll_ctl (in /usr/lib64/libc-2.17.so) ==16375== by 0x4605E5: ??? (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x460129: ??? (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x450A2B: ??? (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x45D431: ngx_single_process_cycle (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== by 0x41CA43: main (in /usr/local/openresty-valgrind/nginx/sbin/nginx) ==16375== Address 0xfff0000a4 is on thread 1's stack ==16375== ==16375== ERROR SUMMARY: 13 errors from 13 contexts (suppressed: 0 from 0)

Add support for providing a function as the key

It would be helpful to be able to provide a function or a string as the key to the verify functions. Such a function would be passed the value of kid from the header as a single parameter, and would return a string to use as the key. This would simplify, for example, using a hash table or a Redis database for looking up keys based off of the kid parameter.

I actually have some code that does this, and am willing to put together a pull request if it would be something that is desired.

Is this project still active?

There are 3 PRs open for 6 months plus. We'd really like to use this project in production in our organisation but it difficult to do so comfortably if it's effectively abandoned.

Module 'resty.aes' not found:

Hi folks, I'm trying to import the lua-resty-jwt in a script but this error module 'resty.aes' not found: is displayed. I tried to find the lua-resty-aes package on luarocks but I could not.

My goal is to build a script to verify a JWT. My current script:

local jwt = require "resty.jwt"

print("Hello World")

Lua version: 5.3.5
OS: MacOS Sierra

Generating a token in php

Does anyone have experience generating the token in php?

I encountered two surprises. No direct support for HS256 in php.
But if I have googled correctly, 'sha256' is the sam3 thing.

Results do not match what is generated by /sign ?

I can use curl to get a result --but that seems weird. Maybe it isn't?!

$payload=[
    'typ'=>"JWT",
    'alg'=>"HS256"
];
$jload = json_encode($payload);

// string hash_hmac ( string $algo , string $data , string $key [, bool $raw_output = false ] )

$key="lua-resty-jwt";

$hash = hash_hmac("sha256", $jload, $key, false);
echo "\nHASHMACF:\n$hash";

$hash = hash_hmac("sha256", $jload,$key, true);
echo "\nHASHMACT:\n$hash";

Appreciate any advice, thank you.
I had assumed I could generate the token in php along with the page.
But worried now I have misunderstood something essential.

no start line - RS256

Greetings.

I'm not sure if it's something I'm completely borking up, but I can't see another way to do it.

We use your library to send JWT's from our frontends to our backends out of paranoia - this is working great, php has no trouble decoding these.

I am now trying to use your library to accept a JWT from a different provider (encoded with go-jwt)

This is the public key

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD0UJybPz2/GQAGh/Ri3rvsv60h
fYB3JzoZpWzKBPPXeHC/w7rnOSdOjeqLT4evgfRXjsld9Rx5GYXARiEicTl1l9DU
+P9xk0eptU/Cawz003/XT5tPX+uhmFcC3zIa3oUb/25Kq+IPeq1AUTK9PcJsohFk
pcgtuZvphRLKvFA8uQIDAQAB
-----END PUBLIC KEY-----

This is the token

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NDQ2OTQxNzksImlhdCI6MTQ0NDY1MDk3OSwiaXAiOiIxNzIuMTYuMjAzLjEiLCJuYmYiOjE0NDQ2NTA5NzksInVzZXIiOiJzaGFubm9uLnd5bnRlciJ9.PLEe64xKFGpkwrX2OameIT6_xSpl5mW_BxFmI4sz20SS_jOaemWrwUBB4gMIUm9LwhxUWTAr5wdLMF30bDJLSfQamf2VeggGMzvjKMQdbJcOPzT6QPmuITAju7bn6WzLzEE5cThIXhOCEdNGKf1xDp9om9T8HVZ15nWpuwwvqwo

This is the output

{"payload":{"nbf":1444650979,"ip":"172.16.203.1","exp":1444694179,"user":"shannon.wynter","iat":1444650979},"reason":"no start line","raw_header":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9","valid":true,"header":{"alg":"RS256","typ":"JWT"},"signature":"PLEe64xKFGpkwrX2OameIT6_xSpl5mW_BxFmI4sz20SS_jOaemWrwUBB4gMIUm9LwhxUWTAr5wdLMF30bDJLSfQamf2VeggGMzvjKMQdbJcOPzT6QPmuITAju7bn6WzLzEE5cThIXhOCEdNGKf1xDp9om9T8HVZ15nWpuwwvqwo","verified":false,"raw_payload":"eyJleHAiOjE0NDQ2OTQxNzksImlhdCI6MTQ0NDY1MDk3OSwiaXAiOiIxNzIuMTYuMjAzLjEiLCJuYmYiOjE0NDQ2NTA5NzksInVzZXIiOiJzaGFubm9uLnd5bnRlciJ9"}```

And this is how I get that

local cookie_value = ngx.var.cookie_lda
if (cookie_value == nil) then
    ngx.exit(401)
else
    local public_key = [[-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD0UJybPz2/GQAGh/Ri3rvsv60h
fYB3JzoZpWzKBPPXeHC/w7rnOSdOjeqLT4evgfRXjsld9Rx5GYXARiEicTl1l9DU
+P9xk0eptU/Cawz003/XT5tPX+uhmFcC3zIa3oUb/25Kq+IPeq1AUTK9PcJsohFk
pcgtuZvphRLKvFA8uQIDAQAB
-----END PUBLIC KEY-----]]

    local jwt_obj = jwt:verify(public_key, cookie_value)
    ngx.log(ngx.ERR, cjson.encode(jwt_obj))
end

The debug tool at jwt.io says it's a verified signature but this library won't verify it saying "no start line"

Any help?

Always get "internal error" for reason

What does it mean when the library returns "internal error" for the reason?

I'm testing with a secret of tacos and following JWT

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjJjOGIzNThhLWM3MTctNGQyYi1hMWZmLWY2YzFmMjQxNWFjNCIsImlhdCI6MTQ0MTUxMzMyNCwiZXhwIjoxNDQxNTMxMzI0fQ.vs-xGa4SC6CuzHyb1bWywTyMEffi5MVILnVC3E-zz-g

Testing on jwt.io shows that the token is fine and the secret works. Here's my lua code (running in NGINX)

cjson = require "cjson"
jwt   = require "resty.jwt"

local parsed_jwt = jwt:verify("tacos", *insert JWT from above*)
ngx.say(cjson.encode(parsed_jwt))

And the output is

{
    "payload":{
        "exp":1441531324,
        "id":"2c8b358a-c717-4d2b-a1ff-f6c1f2415ac4",
        "iat":1441513324
    },
    "reason":"internal error",
    "raw_header":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9",
    "valid":true,
    "header":{"alg":"HS256","typ":"JWT"},
    "signature":"vs-xGa4SC6CuzHyb1bWywTyMEffi5MVILnVC3E-zz-g",
    "verified":false,
    "raw_payload":"eyJpZCI6IjJjOGIzNThhLWM3MTctNGQyYi1hMWZmLWY2YzFmMjQxNWFjNCIsImlhdCI6MTQ0MTUxMzMyNCwiZXhwIjoxNDQxNTMxMzI0fQ"
}

Any ideas? The payload is parsed correctly.

"jwt.lua": HS256 signature validation fails which reason as "internal error"

My env:

After debug i stuck up at hmac.lua at function _M.new(self, key, hash_algo). It seems it returns "nil' to the caller in JWT.lua.
I doubt something going worng with 'require "ffi"' in hmac.lua, though not sure.

Log snippet from nginx error.log:
2018/05/30 13:42:41 [warn] 11009#11009: *1 [lua] jwt.lua:109: my signsecret3, client: 127.0.0.1, server: , request: "POST /pps/v3/core/bookings/createEventBooking HTTP/1.1", host: "localhost:8080"
2018/05/30 13:42:41 [warn] 11009#11009: *1 [lua] hmac.lua:92: new(): inside hmac(in new)secret3, client: 127.0.0.1, server: , request: "POST /pps/v3/core/bookings/createEventBooking HTTP/1.1", host: "localhost:8080"
2018/05/30 13:42:41 [warn] 11009#11009: *1 [lua] nginx-jwt.lua:51: auth(): Invalid token: internal error, client: 127.0.0.1, server: , request: "POST /pps/v3/core/bookings/createEventBooking HTTP/1.1", host: "localhost:8080"

Any help appreciated?

Thanks,
Sanjay

Custom Claim Containing Table Value

I've been working though auth0/nginx-jwt and following what you've done since with jwt-validators, in my own attempt to add one more bit of functionality. I wanted to propose an addition or see if I missed something in my own implementation.

Motivation: validating a claim containing an array/table of values from which one must match. For example, validating Foo as a role in a token containing something of the form:

{
  "roles" : [
     "Foo",
     "Bar"
   ],
   iss: "issuer",
   .
   .
},

I was able to accomplish this combining the validators.any_of with the nginx-jwt method for table_contains, looking something like:

access_by_lua_block {
     jwt = require("auth.nginx-jwt")
     local valid_roles = {"Foo","Bar"}
     .
     .
     local claim_spec = {
         .
         roles = validators.any_of(valid_roles, jwt.table_contains, "roles","table","string")
         .
     }
     .
     .
}

So, I'm proposing a method that uses table_contains in a similar form of equality_function and its friends, altogether allowing to expose a new validator:

validators.contains_any_of(check_values)(opt)

Thoughts?

Question regarding expiration

Hi.

We've been playing with jwt and we've noticed that in your library, you only expire the token if "leeway" is not nil:

     if leeway ~= nil and not jwt_obj["reason"] then

Shouldn't the token be expired regardless of "leeway" being present or not, if "exp" is present in the payload of the token?

Thanks for your attention.

Question on JWT token validation.

I am trying to validate my access_token using https://github.com/pingidentity/lua-resty-openidc#sample-configuration-for-oauth-20-jwt-token-validation. Somehow I wasn't able to succeed. My goal is to protect my apis using this approach. Here is my configuration. Am I missing something here. I am getting 2 issues here. Can someone tell me where I am going wrong. I was able to verify the signature using http://jwt.io

openidc.lua calls https://github.com/pingidentity/lua-resty-openidc/blob/master/lib/resty/openidc.lua#L723

  1. With the below configuration, I am getting openidc_discover(): accessing discovery url (https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration) failed: 20: unable to get local issuer certificate, client:

  2. When I added secret (public key string from my secret.pem file) I am getting "reason":"Verification failed","raw_header":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6InowMzl6ZHNGdWl6cEJmQlZLMVRuMjVRSFlPMCIs

location /api {
      access_by_lua '
          local opts = {
            -- The jwks endpoint must provide a x5c entry
   discovery = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"
          }
          -- call bearer_jwt_verify for OAuth 2.0 JWT validation
          local res, err = require("resty.openidc").bearer_jwt_verify(opts)
..
..
    }
  }
}

I am sending Curl request with authorization token to test if my /api is secured or not like below
curl -i http://myserver.com/api/index.html -H 'Authorization: bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6InowMzl6ZHNGdWl6cEJmQlZLMVRuMjVRSFlPMCIs' @

Validate header claims

I'd like to be able to validate header claims with my claim spec as well -- this could look like:

local claimSpec = {
  header = {
    alg = validators.equals('HS256'),
    kid = validators.equals('MyKeyID'),
    custom = validators.equals('other')
  },
  payload = {
    exp = validators.required(validators.opt_is_not_expired()),
  }
}

Standard claims (such as exp) not validated by default

This was a bummer to me. By default, the following library usage is insecure at not RFC-compliant:

local jwt_obj = jwt:verify(SECRET_KEY, token)

For example, this happily returns jwt_obj.verified being true, even if the token is expired.

I understand that application developers must always be responsible when using security-relevant libraries. But not validating claims by default and not explicitly warning about this in the docs in practice leads to many vulnerable applications.

claim_spec by default really is empty, so that validate_claims() by default just jumps to return true:

for _, claim_spec in ipairs(claim_specs) do

The JWT RFC https://tools.ietf.org/html/rfc7519 specifies the behavior of certain claims such as the exp claim, and I really would have assumed that lua-resty-jwt (like all of the other JWT libraries I have looked at) would validate those claims by default.

What we IMO need to have, at least: a big big warning in the README that calling local jwt_obj = jwt:verify(SECRET_KEY, token) alone leads to a validation process that is

  1. insecure (as of e.g. the missing exp validation)
  2. not RFC-compliant

What would be better: have an example invocation in the docs that leads to RFC-compliant token verification (i.e. a verification that processes all standard claims in the way as specified in RFC 7519).

What would be best, I think: jwt:verify(key, jwt_token) should be secure/RFC-compliant. As simple as that. :)

Eliminate header? Can this be an option?

RE https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/

Apologies if this has already been covered. I am not expert enough with LUA to read the code and be sure I have understood.

If I understand the issue, I am not immediately vulnerable, because my usage is internal (I have a secret key, do not use third-parties and public keys). Cf comment from José Romaniello

But still, my preference would be to remove the header all together. Make for a shorter transmission, and remove any clue about which algorithm was used. Cf comment from Federico Rampazzo

I do not yet have the lua mojo to attempt this. Nor do I ask for it.

Thank you for sharing this ! I am excited to be discovering the world of lua+nginx.

Release version 0.1.3

Are there plans to release version 0.1.3 (which has the validation options in it), or are there other features waiting before a release happens?

RSA512 support?

Is it planned? or can you explain how to add support for RSA512?

Causes for "invalid jwt string" ?

Thanks to all.

I am new to both nginx and lua, so this is probably not an issue.
Just not sure where to turn now.
I am trying to extend the example by adding a user id.
Am I attempting something impossible, or do I need more help with syntax (nginx or lua??)
Many variations tried. The following seems closest...

I get a result for the gentoken location:

curl  http://127.0.0.1/gentoken/123
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIkdXNlciJ9.1fb3i8IsaAXL-syRWQzhiGruOUruCMzUxBxHtig3e8c

But I'm running out of ideas on the "invalid jwt string" response:

curl  http://127.0.0.1/verifytoken/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIkdXNlciJ9.1fb3i8IsaAXL-syRWQzhiGruOUruCMzUxBxHtig3e8c
{"verified":false,"reason":"invalid jwt string"}
        location  ~ ^/gentoken/(?<user>.+)$ {
            add_header X-USER "$user";

            content_by_lua '
                local cjson = require "cjson"
                local jwt = require "resty.jwt"

                local jwt_token = jwt:sign(
                    "lua-resty-jwt",
                    {
                        header={typ="JWT", alg="HS256"},
                        payload={uid="$user"}
                    }
                )
                ngx.say(jwt_token)
            ';
        }
    location ~ ^/verifytoken/(?<tok>.+)$ {
            content_by_lua '
                local cjson = require "cjson"
                local jwt = require "resty.jwt"
                local jwt_obj = jwt:verify("lua-resty-jwt", "$tok")

                ngx.say(cjson.encode(jwt_obj))
            ';
        }

RS256

Getting segmentation fault log on signing a new JWT token using RSA private key and certificate

Add handling of intermediate certs in x5u

I'm thinking about working on this topic.

Currently we're relying on Cert.new() and Cert.verify_trust() that have been initially contributed by @dhiltgen

According to the RFC "The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate.This MAY be followed by additional certificates, with each subsequent certificate being the one used to certify the previous one."

The use case I'm willing to deal with would be as follows

  • the x5u url would return something like

    -----BEGIN CERTIFICATE-----
    CERT_1: Leaf cert
    -----END CERTIFICATE
    -----BEGIN CERTIFICATE-----
    CERT_2: Intermediate cert
    -----END CERTIFICATE
    -----BEGIN CERTIFICATE-----
    CERT_3: Self signed root cert
    -----END CERTIFICATE
    
  • The trusted cert location store would contain the self signed root cert as well (CERT_3).

I've started to study (a bit) the OpenSSL API and think I should be able to send something allowing this. However, that will be my first encounter with OpenSSL and I'd be very grateful for some help.

Would the following sequence of calls do the trick?

  • X509_STORE_load_locations(file_containing_CERT_3)
  • sk_X509_push(CERT_3)
  • sk_X509_push(CERT_2)
  • X509_STORE_CTX_set_chain()
  • X509_STORE_CTX_set_cert()
  • X509_verify_cert()

Besides, I've got the feeling that the evp.lua Cert API would have to be changed (in order to cope with chains of certs), and I'd prefer to potentially have some feedback regarding how it should shaped (once again, no real experience dealing with chain of certs, so I'm not sure I'd properly think of all the potential use cases and corner cases).

On the other hand, would anyone be already cooking some code in order to make this happen, I'd be happy to beta test it 😉

Thoughts?

id_token 'RS256' signature verification failed, client: xxx.xxx.170.207

I am able to validate the token at jwt.io. Signature validation is also true using my public key.

2018/01/15 06:13:11 [debug] 256#256: *78 [lua] openidc.lua:737: openidc_load_jwt_and_verify_crypto(): jwt: {"signature":"Jb9T5QlRDIJMdhcC_jQ9bZlCKuRBEWe9BJd70g0sw1UJbznKwXb46eGsaGgAF1aorsZaEQqefAxkL5rfgp9dPVETyAQrToGD74QloStLq_15oeCLa0wAeJvHGb4x_pEGNqAjLYHu04MaguQfVZsVulM80kxBLCQ7ILJvii06HuU","reason":"too long",

"reason":"too long" - what could be the reason it repeatedly shows "too long" and verification fails on nginx ?

Reference - zmartzone/lua-resty-openidc#135

question about set and check expire time

How to set expire time when create a new jwt token?
And how to check a token is expired? Does below codes working?
local jwt_obj = jwt:verify("jwt-key", token,
{
lifetime_grace_period = 120,
require_exp_claim = true
}
)

New build 0.1.5

Any schedule for getting another release (0.1.5?) with the recent changes?

Got 'everything is awesome~ :p' error

I met a verify error which reason is "interval error", then I modify this line to jwt_obj[str_const.reason] = cjson.decode(ret), then I got a reason "everything is awesome~ :p", is there some logic missed in your code?

Tokens always pass validation even with invalid checksums when using asymmetric key algorithms

Hello,

First of all, let me say that my knowledge of Lua can be measured in "days", so please be gentle :-)

I have been using the library for the last weeks, and I it seems that there is a critical bug in the validation of tokens when using asymmetric algorithms (RS256 or RS512).

In the following code in jwt.lua _M:verify_jwt_object:

...
    if alg == str_const.RS256 then
      local verified, err = verifier:verify(message, sig, evp.CONST.SHA256_DIGEST)
    elseif alg == str_const.RS512 then
      local verified, err = verifier:verify(message, sig, evp.CONST.SHA512_DIGEST)
    end
    if not verified then
      jwt_obj[str_const.reason] = err
    end
  else
    jwt_obj[str_const.reason] = "Unsupported algorithm " .. alg
  end

  if not jwt_obj[str_const.reason] then
    jwt_obj[str_const.verified] = true
    jwt_obj[str_const.reason] = str_const.everything_awesome
  end
  return jwt_obj
...

As far as I understand, this piece of code creates variables verified and err in local block scope, meaning that out of the if-then-else they will be nil, no matter what happens after the verification. Then the next piece of code will set the result verified to true depending on reason, which was assigned to nil from the err value.

The end result seems to be that, no matter which key is passed, when this code path is reached the verification will always succeed.

I did a quick fix on my code with something like the following and it seems to work (please excuse my poor Lua):

    -- a previous check makes sure that 'alg' can only be either 'str_const.RS256'
    -- or 'str_const.RS512'
    local evp_digest = nil
    if alg == str_const.RS256 then
      evp_digest = evp.CONST.SHA256_DIGEST
    else
      evp_digest = evp.CONST.SHA512_DIGEST
    end

    local verified, err = verifier:verify(message, sig, evp_digest)

    if not verified then
      jwt_obj[str_const.reason] = err
    end
  else 
    jwt_obj[str_const.reason] = "Unsupported algorithm " .. alg
  end

  if not jwt_obj[str_const.reason] then
    jwt_obj[str_const.verified] = true
    jwt_obj[str_const.reason] = str_const.everything_awesome
  end
  return jwt_obj

Am I missing anything here?

wrong tag - RS256

Hi,

I have the following JWT token (generated from keycloak ):

eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI5ZTVlYTkxYS05ZDk0LTQ2M2YtYjVlMi00Y2U0YWRjMzM2MzYiLCJleHAiOjE0NTgyOTQ0NzcsIm5iZiI6MCwiaWF0IjoxNDU4Mjk0MTc3LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvVGVzdCIsImF1ZCI6IlRlc3RDbGllbnQiLCJzdWIiOiIzYzFmMjBkOS1kNDgxLTQzN2QtOWZlOC03YzU0YzZhNDY1ZTYiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJUZXN0Q2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjlkZTJlNDYyLThmMGUtNGU3Yi05MmM2LTEzMzc5MDQ4OGM2NiIsImNsaWVudF9zZXNzaW9uIjoiNTI3MDkyODMtYzNmNy00ZGQxLWExMjYtOGRmNjBmNDRhNWU5IiwiYWxsb3dlZC1vcmlnaW5zIjpbXSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJ2aWV3LXByb2ZpbGUiXX19LCJuYW1lIjoiIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidGVzdCJ9.fujEnidGKs1F2RRG-MGKP-aQDa8DqzR3nQDpt-TQDQ1rBbj4xdI2UDFL1tgMNj-V--xf_bKujf0ROM5VUd-AOdR0n5dnmO8pgm02P-0Pc5Ycat2ADJoL3TlJJSE2SkZNPQ-MS3DN2d_bnJak3If3OwlzXH7puOVgxGnjNnWWTm_nZXi2Y1gdUOdMOzbUDR--e2-hDPwZG5PS4xUX_XbMMVhPAwYqsEfpPdYVXgcoF7czjWPFPqtaEXy0BvJashdsy-Tv56wusKrt38tWP5MA6qWxtXNP4Cho-RGdAI6jDgmsJKE-jDSME7vVy-5i6w1NI_hr_4zakOLzySfcsFgYRg

Public key:

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArNMMislfx2MqXNLP5rFOZ5/K54qpI2JitGBoosOuxBGnNS8nbsqDW7VoXEDooR3KrZoJQurxKuvFQ/RnEWpNNd6wZnussIbmQzYKOBhzpu3ksOena7enfxAVTfc5WiuU1+NwP9AEbJJWSPskMkEINluk+aCL5y8d27q3Rooo9bvtcrR4f7RxvHnv1mzaXGP2b7W7AO2qMoiizmZWWKTxeea2RmGniNFHbI2i0SVTQWdW3KKo+iX98SC0HCw8KnFI6wbwABDnel7xLz+LRinw9+Jr9gQdAPFZIY1tKyKiqzGkCnz3N3DPdE3dHON8Mnsh1Lwv9FQurYdwB0vvfH5ZhQIDAQAB

Unfortunately when verifying the token with lua.jwt:
local jwt_obj = jwt:verify(cert, jwt_token)
The token is not verified and the reason is "Wrong tag" which I wasn't able to decipher what it means.

{"payload":{"nbf":0,"azp":"TestClient","client_session":"52709283-c3f7-4dd1-a126-8df60f44a5e9","iat":1458294177,"iss":"http://localhost:8080/auth/realms/Test","aud":"TestClient","preferred_username":"test","name":"","session_state":"9de2e462-8f0e-4e7b-92c6-133790488c66","sub":"3c1f20d9-d481-437d-9fe8-7c54c6a465e6","exp":1458294477,"resource_access":{"account":{"roles":["manage-account","view-profile"]}},"jti":"9e5ea91a-9d94-463f-b5e2-4ce4adc33636","allowed-origins":{},"typ":"Bearer"},"reason":"wrong tag","raw_header":"eyJhbGciOiJSUzI1NiJ9","valid":true,"header":{"alg":"RS256"},"signature":"fujEnidGKs1F2RRG-MGKP-aQDa8DqzR3nQDpt-TQDQ1rBbj4xdI2UDFL1tgMNj-V--xf_bKujf0ROM5VUd-AOdR0n5dnmO8pgm02P-0Pc5Ycat2ADJoL3TlJJSE2SkZNPQ-MS3DN2d_bnJak3If3OwlzXH7puOVgxGnjNnWWTm_nZXi2Y1gdUOdMOzbUDR--e2-hDPwZG5PS4xUX_XbMMVhPAwYqsEfpPdYVXgcoF7czjWPFPqtaEXy0BvJashdsy-Tv56wusKrt38tWP5MA6qWxtXNP4Cho-RGdAI6jDgmsJKE-jDSME7vVy-5i6w1NI_hr_4zakOLzySfcsFgYRg","verified":false,"raw_payload":"eyJqdGkiOiI5ZTVlYTkxYS05ZDk0LTQ2M2YtYjVlMi00Y2U0YWRjMzM2MzYiLCJleHAiOjE0NTgyOTQ0NzcsIm5iZiI6MCwiaWF0IjoxNDU4Mjk0MTc3LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvVGVzdCIsImF1ZCI6IlRlc3RDbGllbnQiLCJzdWIiOiIzYzFmMjBkOS1kNDgxLTQzN2QtOWZlOC03YzU0YzZhNDY1ZTYiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJUZXN0Q2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjlkZTJlNDYyLThmMGUtNGU3Yi05MmM2LTEzMzc5MDQ4OGM2NiIsImNsaWVudF9zZXNzaW9uIjoiNTI3MDkyODMtYzNmNy00ZGQxLWExMjYtOGRmNjBmNDRhNWU5IiwiYWxsb3dlZC1vcmlnaW5zIjpbXSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJ2aWV3LXByb2ZpbGUiXX19LCJuYW1lIjoiIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidGVzdCJ9"}

The same token and public key pass verification in java libraries.

luarocks install lua-resty-jwt fails

Luarocks installation fails.

In my ubuntu desktop machine:

$sudo luarocks install lua-resty-jwt

Warning: falling back to curl - install luasec to get native HTTPS support
Installing https://luarocks.org/lua-resty-jwt-0.1.10-0.src.rock

Error: Couldn't extract archive .: unrecognized filename extension

And in my Dockerfile (based on CentOS):

Step 6/11 : RUN luarocks install lua-resty-jwt
---> Running in dd206fdf553c
Warning: falling back to curl - install luasec to get native HTTPS support

Error: Unrecognized filename extension .
Installing https://luarocks.org/lua-resty-jwt-0.1.10-0.src.rock...
Using https://luarocks.org/lua-resty-jwt-0.1.10-0.src.rock... switching to 'build' mode
The command '/bin/sh -c luarocks install lua-resty-jwt' returned a non-zero code: 1

How to properly cope with certificate renewal?

The lib provides a way to validates certs through jwt:set_trusted_certs_file("/path/to/may/cert.pem").

We're currently assessing the integrity of jwts by

  • first validating the public cert exposed by the x5u claim against a trusted root one (set through jwt:set_trusted_certs_file("/path/to/may/cert.pem"))
  • then checking the signature of the signed jwt

However, it is unclear for me how we should cope with a renewal of the trusted root cert. Should the old one and the new one be concatenated in the .pem file?

Enforcing a stricter kind of validation

Would you be against a stricter kind of jwt validation?

Currently when the exp|nbf claim are not expressed as a number, they're silently ignored.

Two options:

  • Make it stricter by design and fail the verification of the jwt with a meaningful message when exp isn't a number
  • Allow an optional stricter kind of validation (through jwt:enforce_strict_validation(bool) for instance)

When to exect the release of v0.1.7?

I'd like to bundle my project with fixed implementation of jwe. Any ideas when will be a next release? Is there anything that blocks the next release?

required key length is wrong

from rfc

5.2.2.1. AES_CBC_HMAC_SHA2 Encryption
The number of octets in the input key K MUST be the sum of
MAC_KEY_LEN and ENC_KEY_LEN. The values of these parameters are
specified by the Authenticated Encryption algorithms in Sections
5.2.3 through 5.2.5.
5.2.3. AES_128_CBC_HMAC_SHA_256
The input key K is 32 octets long.
ENC_KEY_LEN is 16 octets.
MAC_KEY_LEN is 16 octets.
5.2.5. AES_256_CBC_HMAC_SHA_512 AES_256_CBC_HMAC_SHA_512 is based on AES_128_CBC_HMAC_SHA_256, but
with the following differences: The input key K is 64 octets long instead of 32.
ENC_KEY_LEN is 32 octets instead of 16.
MAC_KEY_LEN is 32 octets instead of 16.

but in code:

local function derive_keys(enc, secret_key)
  local key_size_bytes = 16
  if enc == str_const.A128CBC_HS256 then
    key_size_bytes = 16
  elseif enc == str_const.A256CBC_HS512 then
    key_size_bytes = 32
  end
  if not secret_key then
    secret_key =  resty_random.bytes(key_size_bytes,true)
  end
  if #secret_key ~= key_size_bytes then
    error({reason="The pre-shared content key must be ".. key_size_bytes})
  end
  local derived_key_size = key_size_bytes / 2
  mac_key = string_sub(secret_key, 1, derived_key_size)
  enc_key =string_sub(secret_key, derived_key_size)
  return secret_key, mac_key, enc_key
end

ES{256,384,512} Support

Related to #36.

openssl ecparam -name secp521r1 -genkey -out ES512.pem && openssl ec -in ES512.pem -pubout >> ES512.pem

Comment mention both x5c and x5u being defined when they are not

-- TODO When both x5c and x5u are defined, the implementation should
-- ensure their content match
-- cf. https://tools.ietf.org/html/rfc7517#section-4.6
jwt_obj[str_const.reason] = "Unsupported RS256 key model"
return nil

In the code above, the comment talks about not handling the case of both x5c and x5u are defined. However, it is possible to get that far down in the code path if both x5c and x5u are nil. Is that supposed to be a different case that should be handled?

According to RFC 7515, both x5c and x5u as header parameters are optional it seems...?

Tag releases

Hi - I was wondering what do you think about tagging the releases? It would make easier/more explicit to request a specific version when deploying/building lua-resty-jwt.

It would give you a release page within Github to easily refer to/download versions.

Validation example throws exception due to validate function 3rd argument being a number and not object/table

Been playing around with guard examples and noticed the following:

Getting this exception:

2017/09/12 13:26:07 [error] 7#7: *5 lua entry thread aborted: runtime error: /usr/local/openresty/site/lualib/resty/jwt.lua:696: bad argument #1 to 'pairs' (table expected, got number)
stack traceback:
coroutine 0:
	[C]: in function 'pairs'
	/usr/local/openresty/site/lualib/resty/jwt.lua:696: in function 'validate_claims'
	/usr/local/openresty/site/lualib/resty/jwt.lua:728: in function 'verify'
	/usr/local/openresty/nginx/conf/lua/jwtguard.lua:13: in function </usr/local/openresty/nginx/conf/lua/jwtguard.lua:1>, client: ..., server: localhost, request: "GET /test/ HTTP/1.1", host: "..."

My token does not have any claim and seems like 3rd argument on below line causing this issue:
here

If 3rd argument removed, everything seems to work as expected.

Thanks for a great library!

Plans for luarocks?

I am fairly new to Lua and coming from Ruby it is hard to live without package managers. Any chance you can add the rockspec and publish to luarocks?

Eg.

package = 'lua-resty-jwt'
version = '0.1.2-0'
source = {
  url = "git://github.com/SkyLothar/lua-resty-jwt",
  branch = "master"
}
description = {
  summary = 'JWT for ngx_lua and LuaJIT.',
  detailed = [[
    This library requires an nginx build 
    with OpenSSL, the ngx_lua module, 
    the LuaJIT 2.0, the lua-resty-hmac, 
    and the lua-resty-string,
  ]],
  homepage = 'https://github.com/SkyLothar/lua-resty-jwt',
  license = 'Apache License Version 2'
}
dependencies = {
  'lua >= 5.1',
  'lua-resty-hmac >= 1.0',
  'lua-resty-string >= 0.09',
}
build = {
  type = 'builtin',
  modules = {
    ['resty.jwt'] = 'lib/resty/jwt',
    ['resty.evp'] = 'lib/resty/evp'
  }
}

openssl 1.1 compatibility ?

Hi,

debian will ship nginx with openssl 1.1 support, meaning... one won't be able to
run this module in next's debian stable. Any plans about that ?

Include hmac in distribution?

I don't know if it's possible (for licensing/other reasons), but it would be nice to include the stuff inside of "vendor/resty" in the packaged distribution when builds are made.

Again - not a super urgent issue - and it's something that can be grabbed separately, but it's kind of nice to have a distribution package that contains all the required files.

Push the library on OPM ?

Hello,

Currently, I'm using your library for an another project, I would like to import it from OPM.

Could you push your library on it please ?

I also created a ticket on lua-resty-hmac to ask them to push their project on OPM.

Thank you.

Confused about expected key format

I'm trying to verify a token that uses this jwk:

{
  kid = "20171030-00:00:00",
  alg = "RS256",
  n = "AIueZ4JvoqY1imSaLGLLjCAEe3n/1CdHlnjtRjSLAsK69/ucL3slei3ParNHhuCmhE16LoHwnBXRbjy+GTPjmfLH4HMqbsMrEjGF5FYLW/6JjHJFyUzkWkYh+W/I99FBRC+J2oXSgD0t4iblV4BMYVJljjigMzzTrQT0h2OtXonX2B2vTjwO+nC9oNVI2fY09eDtsyaJOynaduej8Mgo6w8if/q23R0+fQuPxVkY/tr3o6eeUARcVye74mqyPBtNH+Ear894w4ipgSyvl2NAFrGF79CPWypnN8OFPqMMvATXEDeh1VBSnEUqum8bvvjI6S2w32OFxoDQ9zXW5wTMQsU=",
  x5c = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi55ngm+ipjWKZJosYsuMIAR7ef/UJ0eWeO1GNIsCwrr3+5wveyV6Lc9qs0eG4KaETXougfCcFdFuPL4ZM+OZ8sfgcypuwysSMYXkVgtb/omMckXJTORaRiH5b8j30UFEL4nahdKAPS3iJuVXgExhUmWOOKAzPNOtBPSHY61eidfYHa9OPA76cL2g1UjZ9jT14O2zJok7Kdp256PwyCjrDyJ/+rbdHT59C4/FWRj+2vejp55QBFxXJ7viarI8G00f4Rqvz3jDiKmBLK+XY0AWsYXv0I9bKmc3w4U+owy8BNcQN6HVUFKcRSq6bxu++MjpLbDfY4XGgND3NdbnBMxCxQIDAQAB",
  kty = "RSA",
  e = "AQAB"
}

I couldn't find a good way to convert that to a pem in lua so I'm using a node script with the jwk-to-pem library. That gives me a key like this:

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAi55ngm+ipjWKZJosYsuMIAR7ef/UJ0eWeO1GNIsCwrr3+5wveyV6
Lc9qs0eG4KaETXougfCcFdFuPL4ZM+OZ8sfgcypuwysSMYXkVgtb/omMckXJTORa
RiH5b8j30UFEL4nahdKAPS3iJuVXgExhUmWOOKAzPNOtBPSHY61eidfYHa9OPA76
cL2g1UjZ9jT14O2zJok7Kdp256PwyCjrDyJ/+rbdHT59C4/FWRj+2vejp55QBFxX
J7viarI8G00f4Rqvz3jDiKmBLK+XY0AWsYXv0I9bKmc3w4U+owy8BNcQN6HVUFKc
RSq6bxu++MjpLbDfY4XGgND3NdbnBMxCxQIDAQAB
-----END RSA PUBLIC KEY-----

I see this issue #7 that ends with a merged PR about supporting RSA public keys, but I'm still getting the "no start line" error when I call jwt:verify_jwt_obj.

nginx version: openresty/1.13.6.1

lua-resty-jwt
   0.2.0-0 (installed) - /usr/local/openresty/luajit/lib/luarocks/rocks

Verification of claims

The way that the "auth" function of the code at https://github.com/auth0/nginx-jwt is pretty nice. It seems to me that this would be a nicer (and more flexible) alternative to the verification code that is currently being worked on.

Basically, you specify a table with claims you want to verify. Existence of a claim key requires the presence of it. The value of the claims can either be a string (in which case, it's matched against the string value in the payload), or a function which takes a single parameter (the value from the payload) and returns true or false.

It seems that the current validation of nbf and exp (and the grace period and issuers check) could be done using this mechanism instead, if it were available.

I actually have some code that does this, and am willing to put together a pull request if it would be something that is desired.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.