diff --git a/Makefile.am b/Makefile.am index c201b20..d2d2f97 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,11 +31,9 @@ bin_PROGRAMS = \ mconnectctl noinst_PROGRAMS = \ - test-mconn-crypt \ - test-mconn-crypt-vala + test-mconn-crypt -noinst_LTLIBRARIES = \ - libmconn-crypt.la +noinst_LTLIBRARIES = VALAFLAGS = \ --no-color \ @@ -49,6 +47,7 @@ VALAFLAGS = \ --pkg=posix \ --pkg=gdk-3.0 \ --pkg=atspi-2 \ + --pkg=gnutls \ --vapidir=src/crypt #------------------------------------------------------------- @@ -78,64 +77,37 @@ mconnect_SOURCES = \ src/mconnect/config.vala \ src/mconnect/application.vala \ src/mconnect/utils.vala \ - src/mconnect/property-proxy.vala + src/mconnect/property-proxy.vala \ + src/crypt/certificate.vala mconnect_LDADD = \ - libmconn-crypt.la \ $(MCONNECT_LIBS) mconnect_CFLAGS = \ $(MCONNECT_CFLAGS) \ -I$(top_srcdir)/src/crypt -mconnect_VALAFLAGS = \ - --pkg=mconn-crypt - -#------------------------------------------------------------- - -libmconn_crypt_la_SOURCES = \ - src/crypt/mconn-crypt.c \ - src/crypt/mconn-crypt.h - -libmconn_crypt_la_CFLAGS = \ - $(MCONNECT_CFLAGS) - -libmconn_crypt_la_LIBADD = \ - $(MCONNECT_LIBS) - #------------------------------------------------------------- test_mconn_crypt_SOURCES = \ - test/mconn-crypt-test.c + src/crypt/certificate.vala \ + test/mconn-crypt-vala-test.vala test_mconn_crypt_LDADD = \ - $(MCONNECT_LIBS) \ - libmconn-crypt.la + $(MCONNECT_LIBS) test_mconn_crypt_CFLAGS = \ - $(MCONNECT_CFLAGS) \ - -I$(top_srcdir)/src/crypt - -#------------------------------------------------------------- - -test_mconn_crypt_vala_SOURCES = \ - test/mconn-crypt-vala-test.vala - -test_mconn_crypt_vala_LDADD = \ - $(MCONNECT_LIBS) \ - libmconn-crypt.la - -test_mconn_crypt_vala_CFLAGS = \ - $(MCONNECT_CFLAGS) \ - -I$(top_srcdir)/src/crypt + $(MCONNECT_CFLAGS) -test_mconn_crypt_vala_VALAFLAGS = \ - --pkg=mconn-crypt +test_mconn_crypt_VALAFLAGS = \ + --pkg=gio-2.0 \ + --pkg=posix \ + --pkg=gnutls #------------------------------------------------------------- mconnectctl_SOURCES = \ -src/mconnectctl/main.vala + src/mconnectctl/main.vala mconnectctl_LDADD = \ $(MCONNECT_LIBS) @@ -174,7 +146,7 @@ gdb-script: gdb-script.in #------------------------------------------------------------- -GEN_FROM_VALA = $(filter %.vala,$(mconnect_SOURCES)) +GEN_FROM_VALA = $(filter %.vala,$(mconnect_SOURCES) $(test_mconn_crypt_SOURCES)) BUILT_SOURCES = \ mconnect.desktop \ $(GEN_FROM_VALA:.vala=.c) diff --git a/configure.ac b/configure.ac index a6af585..e8e0a48 100644 --- a/configure.ac +++ b/configure.ac @@ -49,6 +49,7 @@ PKG_CHECK_MODULES(MCONNECT, [glib-2.0, libnotify gdk-3.0 atspi-2 + gnutls ]) AC_SUBST(MCONNECT_CFLAGS) AC_SUBST(MCONNECT_LIBS) diff --git a/src/crypt/certificate.vala b/src/crypt/certificate.vala new file mode 100644 index 0000000..c4a2850 --- /dev/null +++ b/src/crypt/certificate.vala @@ -0,0 +1,150 @@ +/* ex:ts=4:sw=4:sts=4:et */ +/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * AUTHORS + * Maciek Borzecki + */ + +namespace Mconn { + + namespace Crypt { + + private GnuTLS.X509.PrivateKey generate_private_key() { + var key = GnuTLS.X509.PrivateKey.create(); + + key.generate(GnuTLS.PKAlgorithm.RSA, 2048); + // size_t sz = 4096; + // var buf = GnuTLS.malloc(sz); + // key.export_pkcs8(GnuTLS.X509.CertificateFormat.PEM, "", + // GnuTLS.X509.PKCSEncryptFlags.PLAIN, + // buf, ref sz); + + // stdout.printf("private key:\n"); + // stdout.printf("%s", (string)buf); + + // GnuTLS.free(buf); + + return key; + } + + private struct dn_setting { + string oid; + string name; + } + + GnuTLS.X509.Certificate generate_self_signed_cert(GnuTLS.X509.PrivateKey key, string common_name) { + + var cert = GnuTLS.X509.Certificate.create(); + var start_time = new DateTime.now_local(); + var end_time = start_time.add_years(10); + + cert.set_key(key); + cert.set_version(1); + cert.set_activation_time((time_t)start_time.to_unix()); + cert.set_expiration_time((time_t)end_time.to_unix()); + uint32 serial = Posix.htonl(10); + cert.set_serial(&serial, sizeof(uint32)); + + dn_setting[] dn = { + dn_setting() { oid=GnuTLS.OID.X520_ORGANIZATION_NAME, + name="mconnect"}, + dn_setting() { oid=GnuTLS.OID.X520_ORGANIZATIONAL_UNIT_NAME, + name="mconnect"}, + dn_setting() { oid=GnuTLS.OID.X520_COMMON_NAME, + name=common_name}, + }; + foreach (var dn_val in dn) { + var err = cert.set_dn_by_oid(dn_val.oid, 0, + dn_val.name.data, dn_val.name.length); + if (err != GnuTLS.ErrorCode.SUCCESS ) { + warning("set dn failed for OID %s - %s, err: %d\n", + dn_val.oid, dn_val.name, err); + } + } + + + var err = cert.sign(cert, key); + GLib.assert(err == GnuTLS.ErrorCode.SUCCESS); + + // size_t sz = 8192; + // var buf = GnuTLS.malloc(sz); + // err = cert.export(GnuTLS.X509.CertificateFormat.PEM, buf, ref sz); + // if (err != GnuTLS.ErrorCode.SUCCESS) { + // if (err == GnuTLS.ErrorCode.SHORT_MEMORY_BUFFER) { + // stdout.printf("too short\n"); + // } else { + // stdout.printf("other error: %d\n", err); + // } + // } else { + // stdout.printf("certificate:\n"); + // stdout.printf("size: %zu\n", sz); + // stdout.printf("%s", (string)buf); + // } + // GnuTLS.free(buf); + + return cert; + } + + private uint8[] export_certificate(GnuTLS.X509.Certificate cert) { + var buf = new uint8[8192]; + size_t sz = buf.length; + + + var err = cert.export(GnuTLS.X509.CertificateFormat.PEM, buf, ref sz); + assert(err == GnuTLS.ErrorCode.SUCCESS); + + debug("actual certificate PEM size: %zu", sz); + debug("certificate PEM:\n%s", (string)buf); + + // TODO: figure out if this is valid at all + buf.length = (int) sz; + + return buf; + } + + private uint8[] export_private_key(GnuTLS.X509.PrivateKey key) { + var buf = new uint8[8192]; + size_t sz = buf.length; + + var err = key.export_pkcs8(GnuTLS.X509.CertificateFormat.PEM, "", + GnuTLS.X509.PKCSEncryptFlags.PLAIN, + buf, ref sz); + assert(err == GnuTLS.ErrorCode.SUCCESS); + debug("actual private key PEM size: %zu", sz); + debug("private key PEM:\n%s", (string)buf); + + // TODO: figure out if this is valid at all + buf.length = (int) sz; + return buf; + } + + private void export_to_file(string path, uint8[] data) throws Error { + var f = File.new_for_path(path); + + f.replace_contents(data, "", false, + FileCreateFlags.PRIVATE | FileCreateFlags.REPLACE_DESTINATION, + null); + } + + public void generate_key_cert(string key_path, string cert_path, string name) throws Error { + var key = generate_private_key(); + var cert = generate_self_signed_cert(key, name); + + export_to_file(cert_path, export_certificate(cert)); + export_to_file(key_path, export_private_key(key)); + } + } +} \ No newline at end of file diff --git a/src/crypt/mconn-crypt.c b/src/crypt/mconn-crypt.c deleted file mode 100644 index a6a19ff..0000000 --- a/src/crypt/mconn-crypt.c +++ /dev/null @@ -1,270 +0,0 @@ -/** - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * AUTHORS - * Maciek Borzecki - */ -#include -#include -#include -#include "mconn-crypt.h" - -/* encrypted data padding */ -#define MCONN_CRYPT_RSA_PADDING RSA_PKCS1_PADDING - -typedef struct _MconnCryptPrivate MconnCryptPrivate; - -/** - * MconnCrypt: - * - * A simple wrapper for cypto operations. - **/ - -struct _MconnCrypt -{ - GObject parent; - MconnCryptPrivate *priv; -}; - -struct _MconnCryptPrivate -{ - RSA *key; /* RSA key wrapper */ -}; - -static void mconn_crypt_dispose (GObject *object); -static void mconn_crypt_finalize (GObject *object); -static gchar *__mconn_get_public_key_as_pem(MconnCryptPrivate *priv); -static gboolean __mconn_load_key(MconnCryptPrivate *priv, const char *path); -static gboolean __mconn_generate_key_at_path(const char *path); - -G_DEFINE_TYPE_WITH_PRIVATE (MconnCrypt, mconn_crypt, G_TYPE_OBJECT); - -static void -mconn_crypt_class_init (MconnCryptClass *klass) -{ - GObjectClass *gobject_class = (GObjectClass *)klass; - - gobject_class->dispose = mconn_crypt_dispose; - gobject_class->finalize = mconn_crypt_finalize; -} - -static void -mconn_crypt_init (MconnCrypt *self) -{ - g_debug("mconn-crypt: new instance"); - self->priv = mconn_crypt_get_instance_private(self); -} - -static void -mconn_crypt_dispose (GObject *object) -{ - MconnCrypt *self = (MconnCrypt *)object; - - if (self->priv->key != NULL) - { - RSA_free(self->priv->key); - self->priv->key = NULL; - } - - G_OBJECT_CLASS (mconn_crypt_parent_class)->dispose (object); -} - -static void -mconn_crypt_finalize (GObject *object) -{ - MconnCrypt *self = (MconnCrypt *)object; - - g_signal_handlers_destroy (object); - G_OBJECT_CLASS (mconn_crypt_parent_class)->finalize (object); -} - -MconnCrypt *mconn_crypt_new_for_key_path(const char *path) -{ - g_debug("mconn-crypt: new crypt for key %s", path); - - MconnCrypt *self = g_object_new(MCONN_TYPE_CRYPT, NULL); - - if (g_file_test(path, G_FILE_TEST_EXISTS) == FALSE) - __mconn_generate_key_at_path(path); - - if (__mconn_load_key(self->priv, path) == FALSE) - { - mconn_crypt_unref(self); - return NULL; - } - - return self; -} - -MconnCrypt * mconn_crypt_ref(MconnCrypt *self) -{ - g_assert(IS_MCONN_CRYPT(self)); - return MCONN_CRYPT(g_object_ref(self)); -} - -void mconn_crypt_unref(MconnCrypt *self) -{ - if (self != NULL) - { - g_assert(IS_MCONN_CRYPT(self)); - g_object_unref(self); - } -} - -GByteArray * mconn_crypt_decrypt(MconnCrypt *self, GBytes *data, GError **err) -{ - g_assert(IS_MCONN_CRYPT(self)); - g_assert(self->priv->key); - - /* g_debug("decrypt: %zu bytes of data", g_bytes_get_size(data)); */ - - g_assert_cmpint(g_bytes_get_size(data), ==, RSA_size(self->priv->key)); - - /* decrypted data is less than RSA_size() long */ - gsize out_buf_size = RSA_size(self->priv->key); - GByteArray *out_data = g_byte_array_sized_new(out_buf_size); - - int dec_size; - dec_size = RSA_private_decrypt(g_bytes_get_size(data), - g_bytes_get_data(data, NULL), - (unsigned char *)out_data->data, - self->priv->key, - MCONN_CRYPT_RSA_PADDING); - /* g_debug("decrypted size: %d", dec_size); */ - g_assert(dec_size != -1); - - g_byte_array_set_size(out_data, dec_size); - - return out_data; -} - -gchar *mconn_crypt_get_public_key_pem(MconnCrypt *self) -{ - g_assert(IS_MCONN_CRYPT(self)); - g_assert(self->priv); - g_assert(self->priv->key); - return __mconn_get_public_key_as_pem(self->priv); -} - -/** - * - */ -static gchar *__mconn_get_public_key_as_pem(MconnCryptPrivate *priv) -{ - gchar *pubkey = NULL; - - /* memory IO */ - BIO *bm = BIO_new(BIO_s_mem()); - - /* generate PEM */ - /* PEM_write_bio_RSAPublicKey(bm, priv->key); */ - PEM_write_bio_RSA_PUBKEY(bm, priv->key); - - /* get PEM as text */ - char *oss_pubkey = NULL; - long data = BIO_get_mem_data(bm, &oss_pubkey); - g_debug("mconn-crypt: public key length: %ld", data); - g_assert(data != 0); - g_assert(oss_pubkey != NULL); - - /* dup the key as buffer goes away with BIO */ - pubkey = g_strndup(oss_pubkey, data); - - BIO_set_close(bm, BIO_CLOSE); - BIO_free(bm); - - return pubkey; -} - -static gboolean __mconn_load_key(MconnCryptPrivate *priv, const char *path) -{ - if (g_file_test(path, G_FILE_TEST_EXISTS) == FALSE) - { - g_critical("mconn-crypt: key file %s does not exist", path); - return FALSE; - } - - g_debug("mconn-crypt: loading key from %s", path); - - BIO *bf = BIO_new_file(path, "r"); - - if (bf == NULL) - { - g_critical("mconn-crypt: failed to open file %s", path); - return FALSE; - } - - RSA *rsa = NULL; - - rsa = PEM_read_bio_RSAPrivateKey(bf, NULL, NULL, NULL); - - BIO_free(bf); - - if (rsa == NULL) - { - g_critical("mconn-crypt: failed to read private key"); - return FALSE; - } - - priv->key = rsa; - - return TRUE; -} - -static gboolean __mconn_generate_key_at_path(const char *path) -{ - gboolean ret = FALSE; - RSA *rsa = NULL; - BIO *bf = NULL; - BIGNUM *e = NULL; - int res = 0; - - rsa = RSA_new(); - g_return_val_if_fail(rsa != NULL, FALSE); - - e = BN_new(); - if (e == NULL) - { - goto cleanup; - } - - BN_set_word(e, RSA_3); - - if (RSA_generate_key_ex(rsa, 2048, e, NULL) != 1) { - g_critical("mconn-crypt: failed to generate RSA key"); - goto cleanup; - } - - bf = BIO_new_file(path, "w"); - if (bf == NULL) - { - g_error("mconn-crypt: failed to open file"); - goto cleanup; - } - - if (PEM_write_bio_RSAPrivateKey(bf, rsa, NULL, NULL, 0, NULL, NULL) == 0) - { - g_critical("mconn-crypt: failed to private write key to file"); - goto cleanup; - } - - ret = TRUE; - - cleanup: - BN_free(e); - RSA_free(rsa); - BIO_free(bf); - - return ret; -} diff --git a/src/crypt/mconn-crypt.h b/src/crypt/mconn-crypt.h deleted file mode 100644 index cb0e634..0000000 --- a/src/crypt/mconn-crypt.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Author: Maciek Borzecki - */ -#ifndef __M_CONN_CRYPT_H__ -#define __M_CONN_CRYPT_H__ - -#include -#include -#include - -G_BEGIN_DECLS - -#define MCONN_TYPE_CRYPT \ - (mconn_crypt_get_type()) -#define MCONN_CRYPT(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ - MCONN_TYPE_CRYPT, \ - MconnCrypt)) -#define MCONN_CRYPT_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST ((klass), \ - MCONN_TYPE_CRYPT, \ - MconnCryptClass)) -#define IS_MCONN_CRYPT(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ - MCONN_TYPE_CRYPT)) -#define IS_MCONN_CRYPT_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE ((klass), \ - MCONN_TYPE_CRYPT)) -#define MCONN_CRYPT_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), \ - MCONN_TYPE_CRYPT, \ - MconnCryptClass)) - -typedef struct _MconnCrypt MconnCrypt; -typedef struct _MconnCryptClass MconnCryptClass; -struct _MconnCryptClass -{ - GObjectClass parent_class; -}; - -GType mconn_crypt_get_type (void) G_GNUC_CONST; - -/** - * mconn_crypt_new_for_key_path: (constructor) - * @path: key path - * - * Returns: (transfer full): new object - */ -MconnCrypt *mconn_crypt_new_for_key_path(const char *path); - -/** - * mconn_crypt_unref: - * @crypt: crypt object - */ -void mconn_crypt_unref(MconnCrypt *crypt); - -/** - * mconn_crypt_ref: - * @crypt: crypt object - * - * Take reference to crypt object - * Returns: (transfer none): reffed object - */ -MconnCrypt *mconn_crypt_ref(MconnCrypt *crypt); - -/** - * mconn_crypt_decrypt: - * @crypt: crypt object - * @data: (type GBytes): data - * @error: return location for a GError or NULL - * - * Returns: (transfer full): a new #GByteArray with decoded data - */ -GByteArray * mconn_crypt_decrypt(MconnCrypt *crypt, GBytes *data, GError **error); - -/** - * mconn_crypt_get_public_key_pem: - * @crypt: crypt object - * - * Returns: (transfer full): allocated string with public key in PEM format - */ -gchar * mconn_crypt_get_public_key_pem(MconnCrypt *crypt); - -G_END_DECLS - -#endif /* __MCONN_CRYPT_H__ */ diff --git a/src/crypt/mconn-crypt.vapi b/src/crypt/mconn-crypt.vapi deleted file mode 100644 index 1c7e218..0000000 --- a/src/crypt/mconn-crypt.vapi +++ /dev/null @@ -1,13 +0,0 @@ -namespace Mconn { - [CCode (cheader_filename = "mconn-crypt.h", type_id = "mconn_crypt_get_type ()")] - public class Crypt : GLib.Object { - [CCode (has_construct_function = false)] - protected Crypt (); - public GLib.ByteArray decrypt (GLib.Bytes data) throws GLib.Error; - [CCode (has_construct_function = false)] - public Crypt.for_key_path (string path); - public string get_public_key_pem (); - public unowned Mconn.Crypt @ref (); - public void unref (); - } -} diff --git a/test/mconn-crypt-vala-test.vala b/test/mconn-crypt-vala-test.vala index 536f4ff..f3243b6 100644 --- a/test/mconn-crypt-vala-test.vala +++ b/test/mconn-crypt-vala-test.vala @@ -1,41 +1,79 @@ using Mconn; -void test_simple() { - string file_path = "/tmp/test-key-vala.pem"; - FileUtils.remove(file_path); - - string pubkey1; - string pubkey2; - - { - assert(FileUtils.test(file_path, FileTest.EXISTS) == false); - var c = new Crypt.for_key_path(file_path); - assert(FileUtils.test(file_path, FileTest.EXISTS) == true); - pubkey1 = c.get_public_key_pem(); - assert(pubkey1 != null); +void test_generate() { + string key_path = "/tmp/test-key-vala.pem"; + string cert_path = "/tmp/test-cert-vala.pem"; + FileUtils.remove(key_path); + FileUtils.remove(cert_path); + + assert(FileUtils.test(key_path, FileTest.EXISTS) == false); + Crypt.generate_key_cert(key_path, cert_path, "foo"); + assert(FileUtils.test(key_path, FileTest.EXISTS) == true); + assert(FileUtils.test(cert_path, FileTest.EXISTS) == true); +} + +void test_generate_load() { + string key_path = "/tmp/test-key-vala.pem"; + string cert_path = "/tmp/test-cert-vala.pem"; + FileUtils.remove(key_path); + FileUtils.remove(cert_path); + + Crypt.generate_key_cert(key_path, cert_path, "bar"); + + try { + var cert = new TlsCertificate.from_files(cert_path, + key_path); + } catch (Error e) { + Test.fail(); } +} - // file should still exist - assert(FileUtils.test(file_path, FileTest.EXISTS) == true); +void test_custom_cn() { + string key_path = "/tmp/test-key-vala.pem"; + string cert_path = "/tmp/test-cert-vala.pem"; + FileUtils.remove(key_path); + FileUtils.remove(cert_path); - { - assert(FileUtils.test(file_path, FileTest.EXISTS) == true); - var c = new Crypt.for_key_path(file_path); - assert(FileUtils.test(file_path, FileTest.EXISTS) == true); - pubkey2 = c.get_public_key_pem(); - assert(pubkey2 != null); + Crypt.generate_key_cert(key_path, cert_path, "custom-cn"); + + uint8[] data; + try { + File.new_for_path(cert_path).load_contents(null, out data, null); + } catch (Error e) { + Test.fail(); } - debug("public key1:\n%s", pubkey1); - debug("public key2:\n%s", pubkey2); - assert(pubkey1 == pubkey2); + var datum = GnuTLS.Datum() { data=data, size=data.length }; + + var cert = GnuTLS.X509.Certificate.create(); + var res = cert.import(ref datum, GnuTLS.X509.CertificateFormat.PEM); + assert(res == GnuTLS.ErrorCode.SUCCESS); + + // verify DN + var dn = new uint8[1024]; + size_t sz = dn.length; + cert.get_dn(dn, ref sz); + debug("dn: %s\n", (string)dn); + + var issuer_dn = new uint8[1024]; + sz = issuer_dn.length; + cert.get_issuer_dn(issuer_dn, ref sz); + debug("dn: %s\n", (string)issuer_dn); + + var subject = (string)dn; + var issuer = (string)issuer_dn; + + // verify that the certificate is self signed + assert(subject == issuer); + // + assert("CN=custom-cn" in subject); } public static void main(string[] args) { Test.init(ref args); - Test.add_func("/mconn-crypt-vala/simple", () => { - test_simple(); - }); + Test.add_func("/mconn-crypt-vala/generated", test_generate); + Test.add_func("/mconn-crypt-vala/load", test_generate_load); + Test.add_func("/mconn-crypt-vala/verify-cn", test_custom_cn); Test.run(); } \ No newline at end of file