ENGINE_* functions

Pavel Shramov <[email protected]> Fri, 14 Mar 2008 14:19:46 +0300
Newsgroups gmane.comp.python.cryptography
Message-ID <[email protected]>
M2Crypto still lacks ENGINE_* functions and bug [1] seem to be
abandoned so I've done another patch. It seem to be superset of one
mentioned in [1] but without Context and init functions modifications.

                Pavel
--
[1] https://bugzilla.osafoundation.org/show_bug.cgi?id=7585
engine.patch (text/x-diff, 8.4 KB)
diff --git a/M2Crypto/Engine.py b/M2Crypto/Engine.py
new file mode 100644
index 0000000..457e8ff
--- /dev/null
+++ b/M2Crypto/Engine.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+# vim: sts=4 sw=4 et
+"""
+M2Crypto wrapper for OpenSSL ENGINE API.
+
+Pavel Shramov
+IMEC MSU
+"""
+
+from M2Crypto import m2, EVP, X509
+
+class Engine:
+    """ Wrapper for ENGINE object. """
+    def __init__(self, id = None, _ptr = None, _pyfree = 1):
+        """ Create new Engine from ENGINE pointer or obtain by id """
+        self._ptr = _ptr
+        self._pyfree = _pyfree
+        if not self._ptr and not id:
+            raise ValueError("No engine id specified")
+        if not self._ptr:
+            self._ptr = m2.engine_by_id(id)
+            if not self._ptr:
+                raise ValueError("Unknown engine: %s" % id)
+
+    def __del__(self):
+        if self._ptr and self._pyfree:
+            m2.engine_free(self._ptr)
+
+    def ctrl_cmd_string(self, cmd, arg, optional = 0):
+        return m2.engine_ctrl_cmd_string(self._ptr, cmd, arg, optional)
+
+    def get_name(self):
+        return m2.engine_get_name(self._ptr)
+
+    def get_id(self):
+        return m2.engine_get_id(self._ptr)
+
+    def set_default(self, methods = m2.ENGINE_METHOD_ALL):
+        return m2.engine_set_default(self._ptr, methods)
+
+    def __engine_load_key(self, func, name, pin = None):
+        ui = m2.ui_openssl()
+        cbd = m2.engine_pkcs11_data_new(pin)
+        kptr = func(self._ptr, name, ui, cbd)
+        if not kptr:
+            return None
+        key = EVP.PKey(kptr, _pyfree = 1)
+        m2.engine_pkcs11_data_free(cbd)
+        return key
+
+    def load_private_key(self, name, pin = None):
+        return self.__engine_load_key(m2.engine_load_private_key, name, pin)
+
+    def load_public_key(self, name, pin = None):
+        return self.__engine_load_key(m2.engine_load_public_key, name, pin)
+
+    def load_certificate(self, name):
+        """ This function may be not implemented by engine!
+        """
+        cptr = m2.engine_load_certificate(self._ptr, name)
+        if not cptr:
+            return None
+        return X509.X509(cptr, _pyfree = 1)
+        
+def load_dynamic_engine(id, sopath):
+    m2.engine_load_dynamic()
+    e = Engine('dynamic')
+    e.ctrl_cmd_string("SO_PATH", sopath)
+    e.ctrl_cmd_string("ID", id)
+    e.ctrl_cmd_string("LIST_ADD", "1")
+    e.ctrl_cmd_string("LOAD", None)
+    return e
+
+
diff --git a/SWIG/_engine.i b/SWIG/_engine.i
new file mode 100644
index 0000000..3c05885
--- /dev/null
+++ b/SWIG/_engine.i
@@ -0,0 +1,174 @@
+/*
+ * -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: syntax=c sts=4 sw=4
+ *
+ * ENGINE functions from engine(3SSL).
+ * 
+ * Pavel Shramov
+ * IMEC MSU
+ */
+%{
+#include <openssl/engine.h>
+#include <openssl/ui.h>
+#include <stdio.h>
+%}
+
+%apply Pointer NONNULL { ENGINE * };
+%apply Pointer NONNULL { const ENGINE * };
+%apply Pointer NONNULL { const char * };
+
+/*
+ * Functions to load different engines
+ */
+%rename(engine_load_builtin_engines) ENGINE_load_builtin_engines;
+extern void ENGINE_load_builtin_engines(void);
+
+%rename(engine_load_dynamic) ENGINE_load_dynamic;
+extern void ENGINE_load_dynamic(void);
+
+%rename(engine_load_openssl) ENGINE_load_openssl;
+extern void ENGINE_load_openssl(void);
+
+/*
+ * Engine allocation functions
+ */
+%rename(engine_new) ENGINE_new;
+extern ENGINE * ENGINE_new();
+
+%rename(engine_by_id) ENGINE_by_id;
+extern ENGINE * ENGINE_by_id(const char *);
+
+%rename(engine_free) ENGINE_free;
+extern int ENGINE_free(ENGINE *);
+
+/*
+ * Engine id/name functions
+ */
+%rename(engine_get_id) ENGINE_get_id;
+extern const char * ENGINE_get_id(const ENGINE *);
+
+%rename(engine_get_name) ENGINE_get_name;
+extern const char * ENGINE_get_name(const ENGINE *);
+
+/*
+ * Engine control functions
+ */
+%clear const char *;
+%rename(engine_ctrl_cmd_string) ENGINE_ctrl_cmd_string;
+extern int ENGINE_ctrl_cmd_string(ENGINE *e, const char *NONNULL, 
+                const char *arg, int cmd_optional);
+
+%apply Pointer NONNULL { const char * };
+
+/*
+ * UI methods. 
+ * XXX: UI_OpenSSL method is static and UI_destroy_method is not needed.
+ */
+%rename(ui_openssl) UI_OpenSSL;
+extern UI_METHOD * UI_OpenSSL();
+
+%rename(ui_destroy_method) UI_destroy_method;
+extern void UI_destroy_method(UI_METHOD *ui_method);
+
+%clear const char *;
+%inline %{
+
+/*
+ * Code from engine-pkcs11 1.4.0 in engine-pkcs11.c
+ *
+
+99  static char *get_pin(UI_METHOD * ui_method, void *callback_data, char *sc_pin,
+100                      int maxlen)
+101 {
+102         UI *ui;
+103         struct {
+104                 const void *password;
+105                 const char *prompt_info;
+106         } *mycb = callback_data;
+107 
+108         if (mycb->password) {
+109                 sc_pin = set_pin(mycb->password);
+110                 return sc_pin;
+111         }
+ 
+ *
+ * So callback_data need to be always provided and have fixed type.
+ * UI method still may be NULL.
+ *
+ * Following functions allocate and free callback data structure with 
+ * optional password set.
+ */
+
+typedef struct {
+    char * password;
+    char * prompt;
+} _cbd_t;
+
+void * engine_pkcs11_data_new(const char *pin) {
+    _cbd_t * cb = (_cbd_t *) malloc(sizeof(_cbd_t));
+    cb->password = 0;
+    if (pin)
+        cb->password = strdup(pin);
+    cb->prompt = 0;
+    return cb;
+}
+
+void engine_pkcs11_data_free(void * vcb) {
+    _cbd_t * cb = (_cbd_t *) vcb;
+    if (!cb)
+        return;
+    if (cb->password)
+        free(cb->password);
+    free(cb);
+}
+
+%}
+%apply Pointer NONNULL { const char * };
+
+/*
+ * Engine key/cert load functions.
+ * See above notice about callback_data.
+ */
+%rename(engine_load_private_key) ENGINE_load_private_key;
+extern EVP_PKEY *ENGINE_load_private_key(ENGINE *e, const char *key_id,
+                    UI_METHOD *ui_method, void *callback_data);
+%rename(engine_load_public_key) ENGINE_load_public_key;
+extern EVP_PKEY *ENGINE_load_public_key(ENGINE *e, const char *key_id,
+                    UI_METHOD *ui_method, void *callback_data);
+
+/*
+ * This function may be not implemented in engine.
+ * pkcs11 engine has this control.
+ */
+%inline %{
+X509 * engine_load_certificate(ENGINE *e, const char * slot) {
+    struct {
+        const char * slot;
+        X509 * cert;
+    } cbd;
+    cbd.slot = slot;
+    cbd.cert = NULL;
+    if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &cbd, NULL, 0))
+        return NULL;
+    return cbd.cert;
+}
+%}
+
+/* These flags are used to control combinations of algorithm (methods)
+ * by bitwise "OR"ing. */
+#define ENGINE_METHOD_RSA		0x0001
+#define ENGINE_METHOD_DSA		0x0002
+#define ENGINE_METHOD_DH		0x0004
+#define ENGINE_METHOD_RAND		0x0008
+#define ENGINE_METHOD_ECDH		0x0010
+#define ENGINE_METHOD_ECDSA		0x0020
+#define ENGINE_METHOD_CIPHERS		0x0040
+#define ENGINE_METHOD_DIGESTS		0x0080
+#define ENGINE_METHOD_STORE		0x0100
+/* Obvious all-or-nothing cases. */
+#define ENGINE_METHOD_ALL		0xFFFF
+#define ENGINE_METHOD_NONE		0x0000
+
+%rename(engine_set_default) ENGINE_set_default;
+extern int ENGINE_set_default(ENGINE *e, unsigned int flags);
+
diff --git a/SWIG/_m2crypto.i b/SWIG/_m2crypto.i
index 41e633c..3c15ac9 100644
--- a/SWIG/_m2crypto.i
+++ b/SWIG/_m2crypto.i
@@ -51,6 +51,7 @@ static PyObject *ssl_set_tmp_rsa_cb_func;
 %include _pkcs7.i
 %include _util.i
 %include _ec.i
+%include _engine.i
 
 #ifdef SWIG_VERSION
 %constant int encrypt = 1;
diff --git a/tests/test_engine.py b/tests/test_engine.py
new file mode 100644
index 0000000..954d807
--- /dev/null
+++ b/tests/test_engine.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+""" Unit tests for M2Crypto.Engine. """
+
+import unittest
+from M2Crypto import Engine, m2
+
+class EngineTestCase(unittest.TestCase):
+
+    privkey = 'tests/rsa.priv.pem'
+    bad_id = '1bea1edfeb97'
+
+    def test_by_id_junk(self):
+        self.assertRaises(ValueError, Engine.Engine, self.bad_id)
+
+    def test_by_id_openssl(self):
+        m2.engine_load_openssl()
+        Engine.Engine('openssl')
+        
+    def test_by_id_dynamic(self):
+        m2.engine_load_dynamic()
+        Engine.Engine('dynamic')
+        
+    def test_load_private(self):
+        e = Engine.Engine('openssl')
+        e.set_default()
+        key = e.load_private_key(self.privkey)
+        assert key is not None
+
+def suite():
+    return unittest.makeSuite(EngineTestCase)
+    
+
+if __name__ == '__main__':
+    unittest.TextTestRunner().run(suite())
+