lmtopenssl.c /size: 16 Kb    last modification: 2024-01-16 10:22
1/*
2    See license.txt in the root of this project.
3*/
4
5# include "luametatex.h"
6# include "lmtoptional.h"
7
8/*
9    This optional is not yet officially supported and mostly serves as playground. One problem with 
10    certificates like this is that one cannot use letsencrypt (and similar free) certificates but 
11    has to go for expensive, vendor locked in ones. Also, in viewers like acribat root certificates
12    are compiled in so ... in the end it's all not that useful in pdf in the perspective of open 
13    and free software. 
14
15    It tooks a bit of work to get the (in the end not that much) code because searching online gives 
16    confusing and even wrong results. So in the end it came down to looking into openssl and a bit of 
17    trial and error. Therefore the following code is likely to evolve. 
18*/
19
20typedef void * BIO;
21typedef void * EVP_PKEY;
22typedef void * X509;
23typedef void * X509_STORE;
24typedef void * pem_password_cb;
25typedef void * PKCS7;
26typedef void * OPENSSL_STACK;
27
28typedef struct openssllib_state_info {
29
30    int initialized;
31    int padding;
32
33    void (*OPENSSL_init_ssl) (
34        int, 
35        void *
36    );
37
38    PKCS7 * (*PKCS7_sign) (
39        X509          *signcert,
40        EVP_PKEY      *pkey,
41        X509         **certs,    
42        BIO           *data,
43        unsigned int   flags
44    );
45
46    int (*PKCS7_final) (
47        PKCS7 *p7, 
48        BIO   *data, 
49        int    flags
50    );
51
52    int (*i2d_PKCS7) (
53        BIO            *bp, 
54        unsigned char **ppout
55    );
56
57    int (*i2d_PKCS7_DIGEST) (
58        BIO            *bp, 
59        unsigned char **ppout
60    );
61
62    int (*i2d_PKCS7_bio) (
63        BIO   *bp, 
64        PKCS7 *a
65    );
66
67    int (*SMIME_write_PKCS7) (
68        BIO   *out,
69        PKCS7 *p7,
70        BIO   *data,
71        int    flags
72    );
73
74    void (*PKCS7_free) (
75        void *
76    );
77
78    void (*X509_free) (
79        void *
80    );
81
82    void (*EVP_PKEY_free) (
83        void *
84    );
85
86    void * (*BIO_new_file) (
87        const char *filename,
88        const char *mode
89    );
90
91    void * (*BIO_new_mem_buf) (
92        const char *data,
93        int         size
94    );
95
96 // int (*BIO_reset) (
97 //     void *
98 // );
99
100    long (*BIO_ctrl) (
101        BIO  *bp, 
102        int   cmd, 
103        long  larg, 
104        void *parg
105    );
106
107    void (*BIO_free) (
108        void *
109    );
110
111    X509 * (*PEM_read_bio_X509) (
112        BIO             *bp,
113        X509            **x, 
114        pem_password_cb *cb, 
115        const char       *u  
116    );
117    
118    EVP_PKEY * (*PEM_read_bio_PrivateKey) (
119        BIO              *bp,
120        EVP_PKEY        **x,  
121        pem_password_cb  *cb, 
122        const char       *u   
123    );
124
125    int (*PKCS7_verify) (
126        PKCS7      *p7, 
127        X509       *certs, // stack 
128        X509_STORE *store, 
129        BIO        *indata, 
130        BIO        *out, 
131        int         flags
132    );
133
134    int (*d2i_PKCS7) (
135        PKCS7      **a, 
136        const char **ppin, // constant unsigned char 
137        long         length
138    );
139
140    int (*d2i_PKCS7_DIGEST) (
141        PKCS7      **a, 
142        const char **ppin, // constant unsigned char 
143        long         length
144    );
145
146 // OPENSSL_STACK (*OPENSSL_sk_new_null) (
147 //     void
148 // );
149 //
150 // int (*OPENSSL_sk_push) (
151 //     OPENSSL_STACK *st, 
152 //     const void    *data
153 // );
154 //
155 // void (*OPENSSL_sk_free) (
156 //     OPENSSL_STACK *st
157 // );
158
159} openssllib_state_info;
160
161# define BIO_CTRL_RESET  1 /* opt - rewind/zero etc */
162# define BIO_CTRL_INFO   3 /* opt - extra tit-bits */
163
164# define PKCS7_DETACHED 0x0040
165# define PKCS7_BINARY   0x0080
166# define PKCS7_STREAM   0x1000
167
168# define PKCS7_NOINTERN 0x0010
169# define PKCS7_NOVERIFY 0x0020
170
171# define SSL_library_init()     openssllib_state.OPENSSL_init_ssl(0, NULL)
172# define BIO_reset(b)           openssllib_state.BIO_ctrl(b,BIO_CTRL_RESET,0,NULL)
173# define BIO_get_mem_data(b,pp) openssllib_state.BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)(pp))
174
175static openssllib_state_info openssllib_state = {
176
177    .initialized             = 0,
178    .padding                 = 0,
179
180    .OPENSSL_init_ssl        = NULL,
181    .PKCS7_sign              = NULL,   
182    .PKCS7_final             = NULL,   
183    .i2d_PKCS7               = NULL,
184    .i2d_PKCS7_bio           = NULL,
185 // .SMIME_write_PKCS7       = NULL,
186    .PKCS7_free              = NULL,
187    .X509_free               = NULL,
188    .EVP_PKEY_free           = NULL,
189    .BIO_new_file            = NULL,
190    .BIO_new_mem_buf         = NULL,
191    .BIO_ctrl                = NULL,
192    .BIO_free                = NULL,
193    .PEM_read_bio_X509       = NULL,
194    .PEM_read_bio_PrivateKey = NULL,
195    .PKCS7_verify            = NULL,
196    .d2i_PKCS7               = NULL,
197    .d2i_PKCS7_DIGEST        = NULL,
198 // .OPENSSL_sk_new_null     = NULL,
199 // .OPENSSL_sk_push         = NULL,
200 // .OPENSSL_sk_free         = NULL,
201
202};
203
204static int openssllib_initialize(lua_State * L)
205{
206    if (! openssllib_state.initialized) {
207        const char *filename_c = lua_tostring(L, 1);
208        const char *filename_s = lua_tostring(L, 2);
209        if (filename_c && filename_s) {
210
211            lmt_library lib_c = lmt_library_load(filename_c);
212            lmt_library lib_s = lmt_library_load(filename_s);
213
214            openssllib_state.OPENSSL_init_ssl        = lmt_library_find(lib_s, "OPENSSL_init_ssl");
215
216            openssllib_state.PKCS7_sign              = lmt_library_find(lib_c, "PKCS7_sign");
217            openssllib_state.PKCS7_final             = lmt_library_find(lib_c, "PKCS7_final");
218            openssllib_state.i2d_PKCS7               = lmt_library_find(lib_c, "i2d_PKCS7");
219            openssllib_state.i2d_PKCS7_bio           = lmt_library_find(lib_c, "i2d_PKCS7_bio");
220         // openssllib_state.SMIME_write_PKCS7       = lmt_library_find(lib_c, "SMIME_write_PKCS7");
221            openssllib_state.PKCS7_free              = lmt_library_find(lib_c, "PKCS7_free");
222
223            openssllib_state.X509_free               = lmt_library_find(lib_c, "X509_free");
224            openssllib_state.EVP_PKEY_free           = lmt_library_find(lib_c, "EVP_PKEY_free");
225            openssllib_state.BIO_new_file            = lmt_library_find(lib_c, "BIO_new_file");
226            openssllib_state.BIO_new_mem_buf         = lmt_library_find(lib_c, "BIO_new_mem_buf");
227            openssllib_state.BIO_ctrl                = lmt_library_find(lib_c, "BIO_ctrl");
228            openssllib_state.BIO_free                = lmt_library_find(lib_c, "BIO_free");
229            openssllib_state.PEM_read_bio_X509       = lmt_library_find(lib_c, "PEM_read_bio_X509");
230            openssllib_state.PEM_read_bio_PrivateKey = lmt_library_find(lib_c, "PEM_read_bio_PrivateKey");
231
232            openssllib_state.PKCS7_verify            = lmt_library_find(lib_c, "PKCS7_verify");
233            openssllib_state.d2i_PKCS7               = lmt_library_find(lib_c, "d2i_PKCS7");
234            openssllib_state.d2i_PKCS7_DIGEST        = lmt_library_find(lib_c, "d2i_PKCS7_DIGEST");
235
236         // openssllib_state.OPENSSL_sk_new_null     = lmt_library_find(lib_c, "OPENSSL_sk_new_null");
237         // openssllib_state.OPENSSL_sk_push         = lmt_library_find(lib_c, "OPENSSL_sk_push");
238         // openssllib_state.OPENSSL_sk_free         = lmt_library_find(lib_c, "OPENSSL_sk_free");
239
240            openssllib_state.initialized = lmt_library_okay(lib_s) && lmt_library_okay(lib_c);
241
242            openssllib_state.OPENSSL_init_ssl(0, NULL);
243        }
244    }
245    lua_pushboolean(L, openssllib_state.initialized);
246    return 1;
247}
248
249/*tex
250    Let's save some bytes and delegate the error strings to the \LUA\ module. 
251*/
252
253typedef enum messages {
254    m_all_is_okay,
255    m_invalid_certificate_file,
256    m_invalid_certificate,
257    m_invalid_private_key,
258    m_invalid_data_file,
259    m_invalid_signature,
260    m_unable_to_open_output_file,
261    m_unable_to_reset_file,
262    m_unable_to_save_signature,
263    m_incomplete_specification,
264    m_library_is_unitialized,
265} messages;
266
267/* 
268    We could zero the password but who knows what is kept in memory by openssl. We just 
269    assume a safe server. 
270*/
271
272static int openssllib_sign(lua_State * L)
273{
274    int message = m_all_is_okay;
275    if (openssllib_state.initialized) {
276        if (lua_type(L, 1) == LUA_TTABLE) {
277            const char *certfile = NULL;
278            const char *datafile = NULL;
279            const char *password = NULL;
280            const char *resultfile = NULL;
281            const char *data = NULL;
282            unsigned char *resultdata = NULL;
283            size_t datasize = 0;
284            size_t resultsize = 0;
285            if (lua_getfield(L, 1, "certfile")   == LUA_TSTRING) { certfile   = lua_tostring (L, -1);            } lua_pop(L, 1);
286            if (lua_getfield(L, 1, "datafile")   == LUA_TSTRING) { datafile   = lua_tostring (L, -1);            } lua_pop(L, 1);
287            if (lua_getfield(L, 1, "data")       == LUA_TSTRING) { data       = lua_tolstring(L, -1, &datasize); } lua_pop(L, 1);
288            if (lua_getfield(L, 1, "password")   == LUA_TSTRING) { password   = lua_tostring (L, -1);            } lua_pop(L, 1);
289            if (lua_getfield(L, 1, "resultfile") == LUA_TSTRING) { resultfile = lua_tostring (L, -1);            } lua_pop(L, 1);
290            if (certfile && password && (data || datafile)) {
291                int okay = 0;
292                BIO *input = NULL;
293                BIO *output = NULL;
294                BIO *certificate = NULL;
295                X509 *x509 = NULL;
296                EVP_PKEY *key = NULL;
297                PKCS7 *p7 = NULL;
298                int flags = PKCS7_DETACHED | PKCS7_STREAM | PKCS7_BINARY; /* without PKCS7_STREAM we crash */
299                certificate = openssllib_state.BIO_new_file(certfile, "rb");
300                if (! certificate) {
301                    message = m_invalid_certificate_file;
302                    goto DONE;
303                }
304                x509 = openssllib_state.PEM_read_bio_X509(certificate, NULL, NULL, password);
305                if (! x509) {
306                    message = m_invalid_certificate;
307                    goto DONE;
308                }
309                if (BIO_reset(certificate) < 0) {
310                    message = m_unable_to_reset_file;
311                    goto DONE;
312                }
313                key = openssllib_state.PEM_read_bio_PrivateKey(certificate, NULL, NULL, password);
314                if (! key) {
315                    message = m_invalid_private_key;
316                    goto DONE;
317                }
318                if (datafile) { 
319                    input = openssllib_state.BIO_new_file(datafile, "rb");
320                } else { 
321                    input = openssllib_state.BIO_new_mem_buf(data, (int) datasize);
322                }
323                if (! input) {
324                    message = m_invalid_data_file;
325                    goto DONE;
326                }
327                p7 = openssllib_state.PKCS7_sign(x509, key, NULL, input, flags);
328                if (! p7) {
329                    message = m_invalid_signature;
330                    goto DONE;
331                }
332                openssllib_state.PKCS7_final(p7, input, flags);
333                if (resultfile) { 
334                    output = openssllib_state.BIO_new_file(resultfile, "wb");
335                    if (! output) {
336                        message = m_unable_to_open_output_file;
337                        goto DONE;
338                    }
339                 // if (! (flags & CMS_STREAM) && (BIO_reset(input) < 0)) { // no need to reset 
340                 //     message = m_unable_to_reset_file;
341                 //     goto DONE;
342                 // }
343                 // if (! openssllib_state.SMIME_write_PKCS7(output, p7, input, flags)) {
344                    if (! openssllib_state.i2d_PKCS7_bio(output, p7)) {
345                        message = m_unable_to_save_signature;
346                        goto DONE;
347                    }
348                } else {
349                    resultsize = openssllib_state.i2d_PKCS7(p7, &resultdata);
350                }
351                okay = 1;
352              DONE:
353                if (certificate) { openssllib_state.BIO_free(certificate); }
354                if (x509)        { openssllib_state.X509_free(x509);       }
355                if (p7)          { openssllib_state.PKCS7_free(p7);        }
356                if (key)         { openssllib_state.EVP_PKEY_free(key);    }
357                if (input)       { openssllib_state.BIO_free(input);       }
358                if (output)      { openssllib_state.BIO_free(output);      }
359                if (okay) {
360                    if (resultdata && resultsize > 0) {
361                        lua_pushboolean(L, 1);
362                        lua_pushlstring(L, (const char *) resultdata, resultsize);
363                        return 2;
364                    } else { 
365                        return 1;
366                    }
367                }
368            }
369        } else { 
370            message = m_incomplete_specification;
371        }
372    } else { 
373        message = m_library_is_unitialized;
374    }
375    lua_pushboolean(L, 0);
376    if (message) { 
377        lua_pushinteger(L, message);
378        return 2;
379    } else { 
380        return 1;
381    }
382}
383
384static int openssllib_verify(lua_State * L)
385{
386    int message = m_all_is_okay;
387    if (openssllib_state.initialized) {
388        const char *certfile = NULL;
389        const char *datafile = NULL;
390        const char *password = NULL;
391        const char *signature = NULL;
392        const char *data = NULL;
393        size_t signaturesize = 0;
394        size_t datasize = 0;
395        if (lua_getfield(L, 1, "certfile")  == LUA_TSTRING) { certfile  = lua_tostring (L, -1);                 } lua_pop(L, 1);
396        if (lua_getfield(L, 1, "datafile")  == LUA_TSTRING) { datafile  = lua_tostring (L, -1);                 } lua_pop(L, 1);
397        if (lua_getfield(L, 1, "data")      == LUA_TSTRING) { data      = lua_tolstring(L, -1, &datasize);      } lua_pop(L, 1);
398        if (lua_getfield(L, 1, "signature") == LUA_TSTRING) { signature = lua_tolstring(L, -1, &signaturesize); } lua_pop(L, 1);
399        if (lua_getfield(L, 1, "password")  == LUA_TSTRING) { password  = lua_tostring (L, -1);                 } lua_pop(L, 1);
400        if (certfile && password && (data || datafile)) {
401            int okay = -1;
402            PKCS7 *p7 = NULL; 
403            BIO *input = NULL; 
404            BIO *certificate = NULL;
405            X509 *x509 = NULL;
406         // OPENSSL_STACK stack = NULL;
407            int flags = PKCS7_NOVERIFY | PKCS7_BINARY;
408            certificate = openssllib_state.BIO_new_file(certfile, "rb");
409            if (! certificate) {
410                message = m_invalid_certificate_file;
411                goto DONE;
412            }
413            x509 = openssllib_state.PEM_read_bio_X509(certificate, NULL, NULL, password);
414            if (! x509) {
415                message = m_invalid_certificate;
416                goto DONE;
417            }
418         // stack = openssllib_state.OPENSSL_sk_new_null();
419         // if (! stack) { 
420         //     message = m_invalid_certificate;
421         //     goto DONE;
422         // }
423         // openssllib_state.OPENSSL_sk_push(stack, x509);
424            if (! openssllib_state.d2i_PKCS7(&p7, &signature, signaturesize)) {
425                message = m_invalid_signature;
426                goto DONE;
427            }
428            if (datafile) { 
429                input = openssllib_state.BIO_new_file(datafile, "rb");
430            } else {
431                input = openssllib_state.BIO_new_mem_buf(data, datasize);
432            }
433            if (! input) {
434                message = m_invalid_data_file;
435                goto DONE;
436            }
437//            okay = openssllib_state.PKCS7_verify(p7, stack, NULL, input, NULL, flags);
438            okay = openssllib_state.PKCS7_verify(p7, NULL, NULL, input, NULL, flags);
439          DONE:
440            if (certificate) { openssllib_state.BIO_free(certificate);  }
441            if (x509)        { openssllib_state.X509_free(x509);        }
442            if (p7)          { openssllib_state.PKCS7_free(p7);         }
443            if (input)       { openssllib_state.BIO_free(input);        }
444         // if (stack)       { openssllib_state.OPENSSL_sk_free(stack); }
445            if (okay > 0) {
446                lua_pushboolean(L, okay);
447                return 1;
448            } else { 
449                message = m_invalid_signature;
450            }
451        }
452    }
453    lua_pushboolean(L, 0);
454    if (message) { 
455        lua_pushinteger(L, message);
456        return 2;
457    } else { 
458        return 1;
459    }
460    return 1;
461}
462
463static int openssllib_getversion(lua_State * L)
464{
465    if (openssllib_state.initialized) {
466        /* todo, if ever */
467        lua_pushinteger(L, 1);
468    } else { 
469        lua_pushinteger(L, 0);
470    }
471    return 1;
472}
473
474static struct luaL_Reg openssllib_function_list[] = {
475    { "initialize", openssllib_initialize },
476    { "sign",       openssllib_sign       },
477    { "verify",     openssllib_verify     },
478    { "getversion", openssllib_getversion },
479    { NULL,         NULL                  },
480};
481
482int luaopen_openssl(lua_State * L)
483{
484    lmt_library_register(L, "openssl", openssllib_function_list);
485    return 0;
486}
487