]> WPIA git - cassiopeia.git/commitdiff
Merge branch 'libs/openssl/local'
authorBenny Baumann <BenBE@geshi.org>
Sun, 18 Jan 2015 17:55:56 +0000 (18:55 +0100)
committerBenny Baumann <BenBE@geshi.org>
Sun, 18 Jan 2015 17:55:56 +0000 (18:55 +0100)
28 files changed:
.gitignore
Makefile
debian/.gitignore [new file with mode: 0644]
debian/README.Debian [deleted file]
debian/cacert-cassiopeia-doc.docs [moved from debian/cassiopeia-doc.docs with 100% similarity]
debian/cacert-cassiopeia-doc.install [moved from debian/cassiopeia-doc.install with 100% similarity]
debian/cacert-cassiopeia.default [new file with mode: 0644]
debian/cacert-cassiopeia.init [new file with mode: 0644]
debian/cacert-cassiopeia.install [new file with mode: 0644]
debian/cacert-cassiopeia.manpages [new file with mode: 0644]
debian/cassiopeia.1 [new file with mode: 0644]
debian/cassiopeia.install [deleted file]
debian/changelog
debian/control
debian/copyright
src/X509.cpp [new file with mode: 0644]
src/X509.h [new file with mode: 0644]
src/database.cpp [new file with mode: 0644]
src/database.h [new file with mode: 0644]
src/main.cpp
src/mysql.cpp [new file with mode: 0644]
src/mysql.h [new file with mode: 0644]
src/signer.h [new file with mode: 0644]
src/simpleOpensslSigner.cpp [new file with mode: 0644]
src/simpleOpensslSigner.h [new file with mode: 0644]
test/Makefile
test/src/main.cpp [new file with mode: 0644]
test/src/sanity.cpp [new file with mode: 0644]

index 9cd9963bf82d5b5ce3ab04b91e42b638a931cf7b..56e0b1c730ec09f2d7678c9bc606a6f877bfdfb4 100644 (file)
 bin
 dep
 obj
+
+# Emacs
+*~
+\#*#
+
+# Devel key files
+*.crt
+*.key
+*.csr
+config.txt
+serial
index 6c982744e383cc21453ce242c50509d4f7568724..ed3e56858ef6fe70bf9326596ae4720046a4967d 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -34,9 +34,13 @@ CXX=${LT_CXX}
 CXX_DEP=${LT_CXX_DEP}
 LD=${LT_LD}
 
-CFLAGS=-O3 -g -flto -Wall -Werror -Wextra -pedantic -std=c++11
+ifneq (,$(filter debug,$(DEB_BUILD_OPTIONS)))
+ADDFLAGS=-DNO_DAEMON
+endif
+
+CFLAGS=-O3 -g -flto -Wall -Werror -Wextra -pedantic -std=c++11 ${ADDFLAGS}
 CXXFLAGS=$(CFLAGS)
-LDFLAGS=-O3 -g -flto
+LDFLAGS=-O3 -g -flto -lmysqlclient -lssl -lcrypto -ldl
 
 SRC_DIR=src
 OBJ_DIR=obj
@@ -80,6 +84,7 @@ endif
 .PHONY: install
 install: build
        ${INSTALL_PROGRAM} bin/cassiopeia ${DESTDIR}/usr/bin/cassiopeia
+       ${INSTALL_DIR} ${DESTDIR}/etc/cacert/cassiopeia
 
 .PHONY: libs
 libs: ${LIBS}
@@ -97,7 +102,7 @@ collissiondetect:
 cassiopeia: bin/cassiopeia
 
 bin/cassiopeia: libs ${FS_OBJ}
-       ${MKDIR} $(shell dirname $@) && ${LT_LD} -o $@ ${FS_OBJ}
+       ${MKDIR} $(shell dirname $@) && ${LT_LD} ${LDFLAGS} -o $@ ${FS_OBJ}
 
 ${DEP_DIR}/%.d: ${SRC_DIR}/%.cpp
        ${MKDIR} $(shell dirname $@) && $(CXX_DEP) $(CXXFLAGS) -M -MF $@ $<
diff --git a/debian/.gitignore b/debian/.gitignore
new file mode 100644 (file)
index 0000000..bc6efc8
--- /dev/null
@@ -0,0 +1,5 @@
+/files
+/cacert-cassiopeia
+/cacert-cassiopeia-doc
+/*.substvars
+/*.debhelper
diff --git a/debian/README.Debian b/debian/README.Debian
deleted file mode 100644 (file)
index eace64d..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-cassiopeia for Debian
----------------------
-
-<possible notes regarding this package - if none, delete this file>
-
- -- CAcert Software Team <cacert-devel@cacert.org>  Tue, 04 Feb 2014 23:09:04 +0100
diff --git a/debian/cacert-cassiopeia.default b/debian/cacert-cassiopeia.default
new file mode 100644 (file)
index 0000000..436a32a
--- /dev/null
@@ -0,0 +1 @@
+START_DAEMON=0
diff --git a/debian/cacert-cassiopeia.init b/debian/cacert-cassiopeia.init
new file mode 100644 (file)
index 0000000..bc5b464
--- /dev/null
@@ -0,0 +1,174 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides:          cassiopeia
+# Required-Start:    $local_fs $network $remote_fs $syslog mysql
+# Required-Stop:     $local_fs $network $remote_fs $syslog mysql
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: <Enter a short description of the software>
+# Description:       <Enter a long description of the software>
+#                    <...>
+#                    <...>
+### END INIT INFO
+
+# Author: CAcert Software Team <cacert-devel@cacert.org>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="cacert's signer software"
+NAME=cacert-cassiopeia
+DAEMON=/usr/bin/cassiopeia
+DAEMON_ARGS=
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+DIR=/var/lib/cacert-gigi
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+if [ "$START_DAEMON" = "0" ]; then
+    echo "Not starting $NAME (as configured in /etc/default/$NAME)";
+    exit 0;
+fi
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+       if [ ! -f /etc/cacert/cassiopeia/cassiopeia.conf ]; then
+               echo Missing cassiopeia-configfile
+                exit 2
+        fi
+       # Return
+       #   0 if daemon has been started
+       #   1 if daemon was already running
+       #   2 if daemon could not be started
+        start-stop-daemon -b --start --quiet --pidfile $PIDFILE -d $DIR --exec $DAEMON --test > /dev/null \
+               || return 1
+       start-stop-daemon -b --start --quiet --pidfile $PIDFILE --make-pidfile -d $DIR -c nobody --exec $DAEMON --no-close -- \
+               >> /var/log/cacert-cassiopeia.log 2>&1 \
+               || return 2
+       # The above code will not work for interpreted scripts, use the next
+       # six lines below instead (Ref: #643337, start-stop-daemon(8) )
+       # start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+       #       --name $NAME --test > /dev/null \
+       #       || return 1
+       # start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+       #       --name $NAME -- $DAEMON_ARGS \
+       #       || return 2
+
+       # Add code here, if necessary, that waits for the process to be ready
+       # to handle requests from services started subsequently which depend
+       # on this one.  As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+       # Return
+       #   0 if daemon has been stopped
+       #   1 if daemon was already stopped
+       #   2 if daemon could not be stopped
+       #   other if a failure occurred
+       start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
+       RETVAL="$?"
+       [ "$RETVAL" = 2 ] && return 2
+       # Wait for children to finish too if this is a daemon that forks
+       # and if the daemon is only ever run from this initscript.
+       # If the above conditions are not satisfied then add some other code
+       # that waits for the process to drop all resources that could be
+       # needed by services started subsequently.  A last resort is to
+       # sleep for some time.
+       start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --pidfile $PIDFILE
+       [ "$?" = 2 ] && return 2
+       # Many daemons don't delete their pidfiles when they exit.
+       rm -f $PIDFILE
+       return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+       #
+       # If the daemon can reload its configuration without
+       # restarting (for example, when it is sent a SIGHUP),
+       # then implement that here.
+       #
+       start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+       return 0
+}
+
+case "$1" in
+  start)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+       do_start
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  stop)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+       do_stop
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  status)
+       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+       ;;
+  #reload|force-reload)
+       #
+       # If do_reload() is not implemented then leave this commented out
+       # and leave 'force-reload' as an alias for 'restart'.
+       #
+       #log_daemon_msg "Reloading $DESC" "$NAME"
+       #do_reload
+       #log_end_msg $?
+       #;;
+  restart|force-reload)
+       #
+       # If the "reload" option is implemented then remove the
+       # 'force-reload' alias
+       #
+       log_daemon_msg "Restarting $DESC" "$NAME"
+       do_stop
+       case "$?" in
+         0|1)
+               do_start
+               case "$?" in
+                       0) log_end_msg 0 ;;
+                       1) log_end_msg 1 ;; # Old process is still running
+                       *) log_end_msg 1 ;; # Failed to start
+               esac
+               ;;
+         *)
+               # Failed to stop
+               log_end_msg 1
+               ;;
+       esac
+       ;;
+  *)
+       #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+       echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+       exit 3
+       ;;
+esac
+:
diff --git a/debian/cacert-cassiopeia.install b/debian/cacert-cassiopeia.install
new file mode 100644 (file)
index 0000000..63e3122
--- /dev/null
@@ -0,0 +1,2 @@
+/usr/bin/cassiopeia
+/etc/cacert/cassiopeia
diff --git a/debian/cacert-cassiopeia.manpages b/debian/cacert-cassiopeia.manpages
new file mode 100644 (file)
index 0000000..d7cbb59
--- /dev/null
@@ -0,0 +1 @@
+debian/cassiopeia.1
diff --git a/debian/cassiopeia.1 b/debian/cassiopeia.1
new file mode 100644 (file)
index 0000000..2d7465e
--- /dev/null
@@ -0,0 +1,16 @@
+.\"                                      Hey, EMACS: -*- nroff -*-
+.\" (C) Copyright 2014 CAcert Software Team <software@cacert.org>,
+.\"
+.TH cassiopeia 1 "November 2, 2014"
+.SH NAME
+cassiopeia \- the CAcert.org signing software
+.SH SYNOPSIS
+.B cassiopeia
+.RI [options]
+.SH DESCRIPTION
+.B cassiopeia
+is the signing software that will be used to operate the CAcert.org system.
+.SH OPTIONS
+.TP
+.B --once
+Only wait for and then sign one certificate and exit instantly.
diff --git a/debian/cassiopeia.install b/debian/cassiopeia.install
deleted file mode 100644 (file)
index 85d1e46..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/usr/bin/cassiopeia
index ee1e9fc1feb1b908d51fd358885888d7bbd25690..7f636b68d243691dd7488af63b406b5f08311888 100644 (file)
@@ -1,4 +1,4 @@
-cassiopeia (0.1) unstable; urgency=low
+cacert-cassiopeia (0.1) unstable; urgency=low
 
   * Initial Release.
 
index 493d9207b295cc7a43937d8e2455627ccf4256d9..6ac64498e5e1cf8e5b7f76bed422322ad29b3c0c 100644 (file)
@@ -1,20 +1,22 @@
-Source: cassiopeia
-Section: unknown
+Source: cacert-cassiopeia
+Section: utils
 Priority: extra
 Maintainer: CAcert Software Team <cacert-devel@cacert.org>
-Build-Depends: debhelper (>= 8.0.0), libtool
+Build-Depends: debhelper (>= 8.0.0), libtool, libmysqlclient-dev (>= 5.5), libssl-dev, libboost-test-dev
 Standards-Version: 3.9.4
 Homepage: https://cacert.org/
 #Vcs-Git: git://git.debian.org/collab-maint/cassiopeia.git
 #Vcs-Browser: http://git.debian.org/?p=collab-maint/cassiopeia.git;a=summary
 
-Package: cassiopeia
+Package: cacert-cassiopeia
 Architecture: any
 Depends: ${shlibs:Depends}, ${misc:Depends}
 Description: CAcert Certificate Signing Software
- This package provides the necessary tools to run a certificate signing instance on https://cacert.org
+ This package provides the necessary tools to run a
+ certificate signing instance on https://cacert.org
 
-Package: cassiopeia-doc
+Package: cacert-cassiopeia-doc
 Architecture: all
 Description: Documentation for the CAcert Certificate Signing Software
- This package provides the necessary tools to run a certificate signing instance on https://cacert.org
+ This package provides the necessary tools to run a
+ certificate signing instance on https://cacert.org
index c25872228b5eba9698434cf4e3d1934d730fa126..31b5a0f0ba67fb25783ffa5c5c736e8598b3ade3 100644 (file)
@@ -1,10 +1,9 @@
 Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 Upstream-Name: cassiopeia
-Source: <url://example.com>
+Source: <https://github.com/CAcertOrg/cacert-cassiopeia>
 
 Files: *
-Copyright: <years> <put author's name and email here>
-           <years> <likewise for another author>
+Copyright: 2014 CAcert Software Team <cacert-devel@cacert.org>
 License: GPL-2.0+
 
 Files: debian/*
@@ -27,8 +26,3 @@ License: GPL-2.0+
  .
  On Debian systems, the complete text of the GNU General
  Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
-
-# Please also look if there are files or directories which have a
-# different copyright/license attached and list them here.
-# Please avoid to pick license terms that are more restrictive than the
-# packaged work, as it may make Debian's contributions unacceptable upstream.
diff --git a/src/X509.cpp b/src/X509.cpp
new file mode 100644 (file)
index 0000000..92d7773
--- /dev/null
@@ -0,0 +1,261 @@
+#include "X509.h"
+
+#include <fstream>
+#include <iostream>
+
+#include <openssl/ssl.h>
+#include <openssl/bio.h>
+#include <openssl/x509v3.h>
+
+X509Req::X509Req( X509_REQ* csr ) {
+    req = std::shared_ptr<X509_REQ>( csr, X509_REQ_free );
+    EVP_PKEY* pkt = X509_REQ_get_pubkey( req.get() );
+
+    if( !pkt ) {
+        throw "Error extracting public key";
+    }
+
+    pk = std::shared_ptr<EVP_PKEY>( pkt, EVP_PKEY_free );
+}
+
+X509Req::X509Req( std::string spkac ) {
+    if( spkac.compare( 0, 6, "SPKAC=" ) != 0 ) {
+        throw "Error: not a SPKAC";
+    }
+
+    spkac = spkac.substr( 6 );
+    NETSCAPE_SPKI* spki_p = NETSCAPE_SPKI_b64_decode( spkac.c_str(), spkac.size() );
+
+    if( !spki_p ) {
+        throw "Error: decode failed";
+    }
+
+    spki = std::shared_ptr<NETSCAPE_SPKI>( spki_p, NETSCAPE_SPKI_free );
+    EVP_PKEY* pkt_p = NETSCAPE_SPKI_get_pubkey( spki.get() );
+
+    if( !pkt_p ) {
+        throw "Error: reading SPKAC Pubkey failed";
+    }
+
+    pk = std::shared_ptr<EVP_PKEY>( pkt_p, EVP_PKEY_free );
+}
+
+int X509Req::verify() {
+    if( !req ) {
+        return NETSCAPE_SPKI_verify( spki.get(), pk.get() );
+    }
+
+    return X509_REQ_verify( req.get(), pk.get() );
+}
+
+std::shared_ptr<EVP_PKEY> X509Req::getPkey() {
+    return pk;
+}
+
+std::shared_ptr<X509Req> X509Req::parse( std::string filename ) {
+    std::shared_ptr<BIO> in = std::shared_ptr<BIO>( BIO_new_mem_buf( const_cast<char*>( filename.c_str() ), -1 ), BIO_free );
+    X509_REQ* req = PEM_read_bio_X509_REQ( in.get(), NULL, NULL, NULL );
+
+    if( !req ) {
+        throw "Error parsing CSR";
+    }
+
+    return std::shared_ptr<X509Req>( new X509Req( req ) );
+}
+
+std::shared_ptr<X509Req> X509Req::parseSPKAC( std::string content ) {
+    return std::shared_ptr<X509Req>( new X509Req( content ) );
+}
+
+int add_ext( std::shared_ptr<X509> issuer, std::shared_ptr<X509> subj, int nid, const char* value ) {
+    X509_EXTENSION* ex;
+    X509V3_CTX ctx;
+
+    /* This sets the 'context' of the extensions. */
+    /* No configuration database */
+    X509V3_set_ctx_nodb( &ctx );
+
+    /* Issuer and subject certs: both the target since it is self signed,
+     * no request and no CRL
+     */
+    X509V3_set_ctx( &ctx, issuer.get(), subj.get(), NULL, NULL, 0 );
+    ex = X509V3_EXT_conf_nid( NULL, &ctx, nid, const_cast<char*>( value ) );
+
+    if( !ex ) {
+        return 0;
+    }
+
+    X509_add_ext( subj.get(), ex, -1 );
+    X509_EXTENSION_free( ex );
+
+    return 1;
+}
+
+X509Cert::X509Cert() {
+    X509* c = X509_new();
+
+    if( !c ) {
+        throw "malloc failed";
+    }
+
+    target = std::shared_ptr<X509>( c, X509_free );
+
+    if( !X509_set_version( c, 2 ) ) {
+        throw "Setting X509-version to 3 failed";
+    }
+
+    X509_NAME* subjectP = X509_NAME_new();
+
+    if( !subjectP ) {
+        throw "malloc failure";
+    }
+
+    subject = std::shared_ptr<X509_NAME>( subjectP, X509_NAME_free );
+}
+
+void X509Cert::addRDN( int nid, std::string data ) {
+    if( ! X509_NAME_add_entry_by_NID( subject.get(), nid, MBSTRING_UTF8, ( unsigned char* )const_cast<char*>( data.data() ), data.size(), -1, 0 ) ) {
+        throw "malloc failure";
+    }
+}
+
+void X509Cert::setIssuerNameFrom( std::shared_ptr<X509> caCert ) {
+    if( !X509_set_issuer_name( target.get(), X509_get_subject_name( caCert.get() ) ) ) {
+        throw "Error setting Issuer name";
+    }
+}
+
+void X509Cert::setPubkeyFrom( std::shared_ptr<X509Req> req ) {
+    std::shared_ptr<EVP_PKEY> pktmp = req->getPkey();
+
+    if( !X509_set_pubkey( target.get(), pktmp.get() ) ) {
+        throw "Setting public key failed.";
+    }
+}
+
+void X509Cert::setSerialNumber( BIGNUM* num ) {
+    BN_to_ASN1_INTEGER( num , target->cert_info->serialNumber );
+}
+
+void X509Cert::setTimes( uint32_t before, uint32_t after ) {
+    X509_gmtime_adj( X509_get_notBefore( target.get() ), before );
+    X509_gmtime_adj( X509_get_notAfter( target.get() ), after );
+}
+
+static X509_EXTENSION* do_ext_i2d( int ext_nid, int crit, ASN1_VALUE* ext_struc ) {
+    unsigned char* ext_der;
+    int ext_len;
+    ASN1_OCTET_STRING* ext_oct;
+    X509_EXTENSION* ext;
+    /* Convert internal representation to DER */
+    ext_der = NULL;
+    ext_len = ASN1_item_i2d( ext_struc, &ext_der, ASN1_ITEM_ptr( ASN1_ITEM_ref( GENERAL_NAMES ) ) );
+
+    if( ext_len < 0 ) {
+        goto merr;
+    }
+
+    if( !( ext_oct = M_ASN1_OCTET_STRING_new() ) ) {
+        goto merr;
+    }
+
+    ext_oct->data = ext_der;
+    ext_oct->length = ext_len;
+
+    ext = X509_EXTENSION_create_by_NID( NULL, ext_nid, crit, ext_oct );
+
+    if( !ext ) {
+        goto merr;
+    }
+
+    M_ASN1_OCTET_STRING_free( ext_oct );
+    return ext;
+
+merr:
+    throw "memerr";
+}
+
+void X509Cert::setExtensions( std::shared_ptr<X509> caCert, std::vector<std::shared_ptr<SAN>>& sans ) {
+    add_ext( caCert, target, NID_basic_constraints, "critical,CA:FALSE" );
+    add_ext( caCert, target, NID_subject_key_identifier, "hash" );
+    add_ext( caCert, target, NID_authority_key_identifier, "keyid,issuer:always" );
+    add_ext( caCert, target, NID_key_usage, "critical,nonRepudiation,digitalSignature,keyEncipherment" );
+    add_ext( caCert, target, NID_ext_key_usage, "clientAuth, serverAuth" );
+    add_ext( caCert, target, NID_info_access, "OCSP;URI:http://ocsp.cacert.org" );
+    add_ext( caCert, target, NID_crl_distribution_points, "URI:http://crl.cacert.org/class3-revoke.crl" );
+
+    if( sans.size() == 0 ) {
+        return;
+    }
+
+    std::shared_ptr<GENERAL_NAMES> gens = std::shared_ptr<GENERAL_NAMES>(
+        sk_GENERAL_NAME_new_null(),
+        []( GENERAL_NAMES * ref ) {
+            if( ref ) {
+                sk_GENERAL_NAME_pop_free( ref, GENERAL_NAME_free );
+            }
+        } );
+
+    for( auto& name : sans ) {
+        GENERAL_NAME* gen = GENERAL_NAME_new();
+
+        if( !gen ) {
+            throw "Malloc failure.";
+        }
+
+        gen->type = name->type == "DNS" ? GEN_DNS : name->type == "email" ? GEN_EMAIL : 0; // GEN_EMAIL;
+
+        if( !gen->type
+                || !( gen->d.ia5 = M_ASN1_IA5STRING_new() )
+                || !ASN1_STRING_set( gen->d.ia5, name->content.data(), name->content.size() ) ) {
+            GENERAL_NAME_free( gen );
+            throw "initing iasting5 failed";
+        }
+
+        sk_GENERAL_NAME_push( gens.get(), gen );
+    }
+
+    X509_EXTENSION* ext = do_ext_i2d( NID_subject_alt_name, 0/*critical*/, ( ASN1_VALUE* )gens.get() );
+
+    X509_add_ext( target.get(), ext, -1 );
+    X509_EXTENSION_free( ext );
+}
+
+std::shared_ptr<SignedCertificate> X509Cert::sign( std::shared_ptr<EVP_PKEY> caKey, std::string signAlg ) {
+    if( !X509_set_subject_name( target.get(), subject.get() ) ) {
+        throw "error setting subject";
+    }
+
+    const EVP_MD* md;
+
+    if( signAlg == "sha512" ) {
+        md = EVP_sha512();
+    } else if( signAlg == "sha384" ) {
+        md = EVP_sha384();
+    } else if( signAlg == "sha256" ) {
+        md = EVP_sha256();
+    } else if( signAlg == "sha1" ) {
+        md = EVP_sha1();
+    } else {
+        throw "Unknown md-type";
+    }
+
+    if( !X509_sign( target.get(), caKey.get(), md ) ) {
+        throw "Signing failed.";
+    }
+
+    //X509_print_fp( stdout, target.get() );
+
+    std::shared_ptr<BIO> mem = std::shared_ptr<BIO>( BIO_new( BIO_s_mem() ), BIO_free );
+    PEM_write_bio_X509( mem.get(), target.get() );
+    BUF_MEM* buf;
+    BIO_get_mem_ptr( mem.get(), &buf );
+    std::shared_ptr<SignedCertificate> res = std::shared_ptr<SignedCertificate>( new SignedCertificate() );
+    res->certificate = std::string( buf->data, buf->data + buf->length );
+    BIGNUM* ser = ASN1_INTEGER_to_BN( target->cert_info->serialNumber, NULL );
+    char* serStr = BN_bn2hex( ser );
+    res->serial = std::string( serStr );
+    OPENSSL_free( serStr );
+    BN_free( ser );
+    return res;
+}
diff --git a/src/X509.h b/src/X509.h
new file mode 100644 (file)
index 0000000..ba565fe
--- /dev/null
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include <openssl/ssl.h>
+
+#include "database.h"
+
+class X509Req {
+private:
+    std::shared_ptr<EVP_PKEY> pk;
+    std::shared_ptr<X509_REQ> req;
+    std::shared_ptr<NETSCAPE_SPKI> spki;
+    X509Req( X509_REQ* csr );
+    X509Req( std::string spkac );
+public:
+    static std::shared_ptr<X509Req> parse( std::string filename );
+    static std::shared_ptr<X509Req> parseSPKAC( std::string filename );
+    int verify();
+    std::shared_ptr<EVP_PKEY> getPkey();
+};
+
+class X509Cert {
+private:
+    std::shared_ptr<X509> target;
+    std::shared_ptr<X509_NAME> subject;
+public:
+    X509Cert();
+    void addRDN( int nid, std::string data );
+    void setIssuerNameFrom( std::shared_ptr<X509> ca );
+    void setPubkeyFrom( std::shared_ptr<X509Req> r );
+    void setSerialNumber( BIGNUM* num );
+    void setExtensions( std::shared_ptr<X509> caCert, std::vector<std::shared_ptr<SAN>>& sans );
+    void setTimes( uint32_t before, uint32_t after );
+    std::shared_ptr<SignedCertificate> sign( std::shared_ptr<EVP_PKEY> caKey, std::string signAlg );
+};
diff --git a/src/database.cpp b/src/database.cpp
new file mode 100644 (file)
index 0000000..7eabc17
--- /dev/null
@@ -0,0 +1 @@
+#include "database.h"
diff --git a/src/database.h b/src/database.h
new file mode 100644 (file)
index 0000000..b438187
--- /dev/null
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <string>
+#include <memory>
+#include <vector>
+
+struct Profile {
+    std::string cert;
+    std::string key;
+};
+
+struct Job {
+    std::string id;
+    std::string target;
+    std::string task;
+    std::string from;
+    std::string to;
+};
+
+struct SAN {
+    std::string content;
+    std::string type;
+};
+
+struct AVA {
+    std::string name;
+    std::string value;
+};
+
+struct TBSCertificate {
+    std::string md;
+    std::string profile;
+    std::string csr;
+    std::string csr_type;
+    std::string csr_content;
+    std::vector<std::shared_ptr<SAN>> SANs;
+    std::vector<std::shared_ptr<AVA>> AVAs;
+};
+
+
+struct SignedCertificate {
+    std::string certificate;
+    std::string serial;
+    uint32_t before;
+    uint32_t after;
+    std::string pkHash;
+    std::string certHash;
+    std::string crt_name;
+};
+
+class JobProvider {
+public:
+    virtual std::shared_ptr<Job> fetchJob() = 0;
+    virtual bool finishJob( std::shared_ptr<Job> job ) = 0;
+    virtual std::shared_ptr<TBSCertificate> fetchTBSCert( std::shared_ptr<Job> job ) = 0;
+    virtual void writeBack( std::shared_ptr<Job> job, std::shared_ptr<SignedCertificate> res ) = 0;
+};
index 854bf8fd71ad198d7dcfe775e128394be64f1e0c..cf4f383d843e396b1850e87e2a2aeccc05a9eea9 100644 (file)
-/*
-    Cassiopeia - CAcert signing module
-    Copyright (C) 2014  CAcert Inc.
+#include <sys/stat.h>
+#include <unistd.h>
 
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
+#include <iostream>
+#include <fstream>
+#include <streambuf>
 
-    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.
+#include "database.h"
+#include "mysql.h"
+#include "simpleOpensslSigner.h"
 
-    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.
- */
+#ifdef NO_DAEMON
+#define DAEMON false
+#else
+#define DAEMON true
+#endif
+
+std::string keyDir;
+std::vector<Profile> profiles;
+std::string sqlHost, sqlUser, sqlPass, sqlDB;
+
+std::string writeBackFile( uint32_t serial, std::string cert ) {
+    std::string filename = "keys";
+    mkdir( filename.c_str(), 0755 );
+    filename += "/crt";
+    mkdir( filename.c_str(), 0755 );
+    filename += "/" + std::to_string( serial / 1000 );
+    mkdir( filename.c_str(), 0755 );
+    filename += "/" + std::to_string( serial ) + ".crt";
+    std::ofstream file;
+    file.open( filename.c_str() );
+    file << cert.c_str();
+    file.close();
+    std::cout << "wrote to " << filename << std::endl;
+    return filename;
+}
 
 int main( int argc, const char* argv[] ) {
-    ( void )argc;
-    ( void )argv;
+    ( void ) argc;
+    ( void ) argv;
+    bool once = false;
+
+    if( argc == 2 && std::string( "--once" ) == std::string( argv[1] ) ) {
+        once = true;
+    }
+
+    std::ifstream config;
+    if(DAEMON){
+      config.open( "/etc/cacert/cassiopeia/cassiopeia.conf" );
+    }else{
+      config.open( "config.txt" );
+    }
+
+    if( !config.is_open() ) {
+        std::cerr << "config missing" << std::endl;
+        return 1;
+    }
+
+    std::string line1;
+
+    while( config >> line1 ) {
+        if( line1[0] == '#' ) {
+            continue;
+        }
+
+        int splitter = line1.find( "=" );
+
+        if( splitter == -1 ) {
+            std::cerr << "Ignoring malformed config line: " << line1 << std::endl;
+            continue;
+        }
+
+        std::string key = line1.substr( 0, splitter );
+        std::string value = line1.substr( splitter + 1 );
+
+        if( key == "key.directory" ) {
+            keyDir = value;
+            continue;
+        } else if( key == "sql.host" ) {
+            sqlHost = value;
+        } else if( key == "sql.user" ) {
+            sqlUser = value;
+        } else if( key == "sql.password" ) {
+            sqlPass = value;
+        } else if( key == "sql.database" ) {
+            sqlDB = value;
+        }
+
+        if( key.compare( 0, 8, "profile." ) == 0 ) {
+            int numE = key.find( ".", 9 );
+
+            if( numE == 0 ) {
+                std::cout << "invalid line: " << line1 << std::endl;
+                continue;
+            }
+
+            unsigned int i = atoi( key.substr( 8, numE - 8 ).c_str() );
+            std::string rest = key.substr( numE + 1 );
+
+            if( i + 1 > profiles.size() ) {
+                profiles.resize( i + 1 );
+            }
+
+            if( rest == "key" ) {
+                profiles[i].key = value;
+            } else if( rest == "cert" ) {
+                profiles[i].cert = value;
+            } else {
+                std::cout << "invalid line: " << line1 << std::endl;
+                continue;
+            }
+        }
+    }
+
+    std::cout << profiles.size() << " profiles loaded." << std::endl;
+
+    if( keyDir == "" ) {
+        std::cerr << "Missing config property key.directory" << std::endl;
+        return -1;
+    }
+
+    config.close();
+
+    std::shared_ptr<JobProvider> jp( new MySQLJobProvider( sqlHost, sqlUser, sqlPass, sqlDB ) );
+    std::shared_ptr<Signer> sign( new SimpleOpensslSigner() );
+
+    while( true ) {
+        std::shared_ptr<Job> job = jp->fetchJob();
+
+        if( !job ) {
+            std::cout << "Nothing to work on" << std::endl;
+            sleep( 5 );
+            continue;
+        }
+
+        if( job->task == "sign" ) {
+            try {
+                std::shared_ptr<TBSCertificate> cert = jp->fetchTBSCert( job );
+
+                if( !cert ) {
+                    std::cout << "wasn't able to load CSR" << std::endl;
+                    return 2;
+                }
+
+                std::cout << "Found a CSR at '" << cert->csr << "' signing" << std::endl;
+                std::ifstream t( cert->csr );
+                cert->csr_content = std::string( std::istreambuf_iterator<char>( t ), std::istreambuf_iterator<char>() );
+
+                std::shared_ptr<SignedCertificate> res = sign->sign( cert );
+                std::string fn = writeBackFile( atoi( job->target.c_str() ), res->certificate );
+                res->crt_name = fn;
+                jp->writeBack( job, res );
+            } catch( const char* c ) {
+                std::cerr << "ERROR: " << c << std::endl;
+                return 2;
+            } catch( std::string c ) {
+                std::cerr << "ERROR: " << c << std::endl;
+                return 2;
+            }
+        } else {
+            std::cout << "Unknown job type" << job->task << std::endl;
+        }
+
+        if( DAEMON && !jp->finishJob( job ) ) {
+            return 1;
+        }
 
-    return 0;
+        if( !DAEMON || once ) {
+            return 0;
+        }
+    }
 }
diff --git a/src/mysql.cpp b/src/mysql.cpp
new file mode 100644 (file)
index 0000000..bd6929e
--- /dev/null
@@ -0,0 +1,281 @@
+#include "mysql.h"
+
+#include <stdio.h>
+
+#include <iostream>
+
+#include <mysql/errmsg.h>
+
+//This static variable exists to handle initializing and finalizing the MySQL driver library
+std::shared_ptr<int> MySQLJobProvider::lib_ref(
+    //Initializer: Store the return code as a pointer to an integer
+    new int( mysql_library_init( 0, NULL, NULL ) ),
+    //Finalizer: Check the pointer and free resources
+    []( int* ref ) {
+        if( !ref ) {
+            //The library is not initialized
+            return;
+        }
+
+        if( *ref ) {
+            //The library did return an error when initializing
+            delete ref;
+            return;
+        }
+
+        delete ref;
+
+        mysql_library_end();
+    } );
+
+MySQLJobProvider::MySQLJobProvider( const std::string& server, const std::string& user, const std::string& password, const std::string& database ) {
+    if( !lib_ref || *lib_ref ) {
+        throw "MySQL library not initialized!";
+    }
+
+    connect( server, user, password, database );
+}
+
+MySQLJobProvider::~MySQLJobProvider() {
+    disconnect();
+}
+
+bool MySQLJobProvider::connect( const std::string& server, const std::string& user, const std::string& password, const std::string& database ) {
+    if( conn ) {
+        if( !disconnect() ) {
+            return false;
+        }
+
+        conn.reset();
+    }
+
+    conn = _connect( server, user, password, database );
+
+    return !!conn;
+}
+
+std::shared_ptr<MYSQL> MySQLJobProvider::_connect( const std::string& server, const std::string& user, const std::string& password, const std::string& database ) {
+    MYSQL* tmp( mysql_init( NULL ) );
+
+    if( !tmp ) {
+        return std::shared_ptr<MYSQL>();
+    }
+
+    tmp = mysql_real_connect( tmp, server.c_str(), user.c_str(), password.c_str(), database.c_str(), 3306, NULL, CLIENT_COMPRESS );
+
+    if( !tmp ) {
+        return std::shared_ptr<MYSQL>();
+    }
+
+    auto l = lib_ref;
+    return std::shared_ptr<MYSQL>(
+        tmp,
+        [l]( MYSQL * c ) {
+            if( c ) {
+                mysql_close( c );
+            }
+        } );
+}
+
+bool MySQLJobProvider::disconnect() {
+    if( !conn ) {
+        return false;
+    }
+
+    conn.reset();
+
+    return true;
+}
+
+std::pair< int, std::shared_ptr<MYSQL_RES> > MySQLJobProvider::query( const std::string& query ) {
+    if( !conn ) {
+        return std::make_pair( CR_SERVER_LOST, std::shared_ptr<MYSQL_RES>() );
+    }
+
+    int err = mysql_real_query( this->conn.get(), query.c_str(), query.size() );
+
+    if( err ) {
+        throw std::string( "MySQL error: " ) + mysql_error( this->conn.get() );
+    }
+
+    auto c = conn;
+    std::shared_ptr<MYSQL_RES> res(
+        mysql_store_result( conn.get() ),
+        [c]( MYSQL_RES * r ) {
+            if( !r ) {
+                return;
+            }
+
+            mysql_free_result( r );
+        } );
+
+    return std::make_pair( err, res );
+}
+
+std::shared_ptr<Job> MySQLJobProvider::fetchJob() {
+    std::string q = "SELECT id, targetId, task, executeFrom, executeTo FROM jobs WHERE state='open'";
+
+    int err = 0;
+    std::shared_ptr<MYSQL_RES> res;
+
+    std::tie( err, res ) = query( q );
+
+    if( err ) {
+        return std::shared_ptr<Job>();
+    }
+
+    unsigned int num = mysql_num_fields( res.get() );
+
+    MYSQL_ROW row = mysql_fetch_row( res.get() );
+
+    if( !row ) {
+        return std::shared_ptr<Job>();
+    }
+
+    std::shared_ptr<Job> job( new Job() );
+
+    unsigned long* l = mysql_fetch_lengths( res.get() );
+
+    if( !l ) {
+        return std::shared_ptr<Job>();
+    }
+
+    job->id = std::string( row[0], row[0] + l[0] );
+    job->target = std::string( row[1], row[1] + l[1] );
+    job->task = std::string( row[2], row[2] + l[2] );
+    job->from = std::string( row[3], row[3] + l[3] );
+    job->to = std::string( row[4], row[4] + l[4] );
+
+    for( unsigned int i = 0; i < num; i++ ) {
+        printf( "[%.*s] ", ( int ) l[i], row[i] ? row[i] : "NULL" );
+    }
+
+    printf( "\n" );
+
+    return job;
+}
+
+std::string MySQLJobProvider::escape_string( const std::string& target ) {
+    if( !conn ) {
+        throw "Not connected!";
+    }
+
+    std::string result;
+
+    result.resize( target.size() * 2 );
+
+    long unsigned int len = mysql_real_escape_string( conn.get(), const_cast<char*>( result.data() ), target.c_str(), target.size() );
+
+    result.resize( len );
+
+    return result;
+}
+
+bool MySQLJobProvider::finishJob( std::shared_ptr<Job> job ) {
+    if( !conn ) {
+        return false;
+    }
+
+    std::string q = "UPDATE jobs SET state='done' WHERE id='" + this->escape_string( job->id ) + "' LIMIT 1";
+
+    if( query( q ).first ) {
+        return false;
+    }
+
+    return true;
+}
+
+std::shared_ptr<TBSCertificate> MySQLJobProvider::fetchTBSCert( std::shared_ptr<Job> job ) {
+    std::shared_ptr<TBSCertificate> cert = std::shared_ptr<TBSCertificate>( new TBSCertificate() );
+    std::string q = "SELECT md, profile, csr_name, csr_type FROM certs WHERE id='" + this->escape_string( job->target ) + "'";
+
+    int err = 0;
+
+    std::shared_ptr<MYSQL_RES> res;
+
+    std::tie( err, res ) = query( q );
+
+    if( err ) {
+        return std::shared_ptr<TBSCertificate>();
+    }
+
+    MYSQL_ROW row = mysql_fetch_row( res.get() );
+
+    if( !row ) {
+        return std::shared_ptr<TBSCertificate>();
+    }
+
+    unsigned long* l = mysql_fetch_lengths( res.get() );
+
+    if( !l ) {
+        return std::shared_ptr<TBSCertificate>();
+    }
+
+    cert->md = std::string( row[0], row[0] + l[0] );
+    cert->profile = std::string( row[1], row[1] + l[1] );
+    cert->csr = std::string( row[2], row[2] + l[2] );
+    cert->csr_type = std::string( row[3], row[3] + l[3] );
+
+    cert->SANs = std::vector<std::shared_ptr<SAN>>();
+
+    q = "SELECT contents, type FROM subjectAlternativeNames WHERE certId='" + this->escape_string( job->target ) + "'";
+    std::tie( err, res ) = query( q );
+
+    if( err ) {
+        std::cout << mysql_error( this->conn.get() );
+        return std::shared_ptr<TBSCertificate>();
+    }
+
+    std::cout << "Fetching SANs" << std::endl;
+
+    while( ( row = mysql_fetch_row( res.get() ) ) ) {
+        unsigned long* l = mysql_fetch_lengths( res.get() );
+
+        if( !l ) {
+            return std::shared_ptr<TBSCertificate>();
+        }
+
+        std::shared_ptr<SAN> nSAN = std::shared_ptr<SAN>( new SAN() );
+        nSAN->content = std::string( row[0], row[0] + l[0] );
+        nSAN->type = std::string( row[1], row[1] + l[1] );
+        cert->SANs.push_back( nSAN );
+    }
+
+    q = "SELECT name, value FROM certAvas WHERE certid='" + this->escape_string( job->target ) + "'";
+    std::tie( err, res ) = query( q );
+
+    if( err ) {
+        std::cout << mysql_error( this->conn.get() );
+        return std::shared_ptr<TBSCertificate>();
+
+    }
+
+    while( ( row = mysql_fetch_row( res.get() ) ) ) {
+        unsigned long* l = mysql_fetch_lengths( res.get() );
+
+        if( !l ) {
+            return std::shared_ptr<TBSCertificate>();
+        }
+
+        std::shared_ptr<AVA> nAVA = std::shared_ptr<AVA>( new AVA() );
+        nAVA->name = std::string( row[0], row[0] + l[0] );
+        nAVA->value = std::string( row[1], row[1] + l[1] );
+        cert->AVAs.push_back( nAVA );
+    }
+
+    return cert;
+}
+
+void MySQLJobProvider::writeBack( std::shared_ptr<Job> job, std::shared_ptr<SignedCertificate> res ) {
+    if( !conn ) {
+        throw "Error while writing back";
+    }
+
+    std::string q = "UPDATE certs SET crt_name='" + this->escape_string( res->crt_name ) + "', serial='" + this->escape_string( res->serial ) + "', created=NOW() WHERE id='" + this->escape_string( job->target ) + "' LIMIT 1";
+
+    // TODO write more thingies back
+
+    if( query( q ).first ) {
+        throw "Error while writing back";
+    }
+}
diff --git a/src/mysql.h b/src/mysql.h
new file mode 100644 (file)
index 0000000..da13e58
--- /dev/null
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <string>
+#include <memory>
+#include <tuple>
+
+#include <mysql/mysql.h>
+
+#include "database.h"
+
+class MySQLJobProvider : public JobProvider {
+private:
+    static std::shared_ptr<int> lib_ref;
+
+    std::shared_ptr<MYSQL> conn;
+
+private:
+    std::shared_ptr<MYSQL> _connect( const std::string& server, const std::string& user, const std::string& password, const std::string& database );
+
+public:
+    MySQLJobProvider( const std::string& server, const std::string& user, const std::string& password, const std::string& database );
+    ~MySQLJobProvider();
+
+public:
+    bool connect( const std::string& server, const std::string& user, const std::string& password, const std::string& database );
+    bool disconnect();
+
+    std::string escape_string( const std::string& target );
+
+    std::pair< int, std::shared_ptr<MYSQL_RES> > query( const std::string& query );
+
+public:
+    std::shared_ptr<Job> fetchJob();
+    bool finishJob( std::shared_ptr<Job> job );
+    std::shared_ptr<TBSCertificate> fetchTBSCert( std::shared_ptr<Job> job );
+    void writeBack( std::shared_ptr<Job> job, std::shared_ptr<SignedCertificate> res );
+};
diff --git a/src/signer.h b/src/signer.h
new file mode 100644 (file)
index 0000000..feef5da
--- /dev/null
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <memory>
+
+#include "database.h"
+
+class Signer {
+public:
+    virtual std::shared_ptr<SignedCertificate> sign( std::shared_ptr<TBSCertificate> cert ) = 0;
+};
diff --git a/src/simpleOpensslSigner.cpp b/src/simpleOpensslSigner.cpp
new file mode 100644 (file)
index 0000000..eb7d8b9
--- /dev/null
@@ -0,0 +1,196 @@
+#include "simpleOpensslSigner.h"
+
+#include <iostream>
+#include <fstream>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/bio.h>
+#include <openssl/bn.h>
+#include <openssl/engine.h>
+#include <openssl/x509v3.h>
+
+#include "X509.h"
+
+extern std::vector<Profile> profiles;
+
+std::shared_ptr<int> SimpleOpensslSigner::lib_ref(
+    new int( SSL_library_init() ),
+    []( int* ref ) {
+        delete ref;
+
+        EVP_cleanup();
+        CRYPTO_cleanup_all_ex_data();
+    } );
+
+std::shared_ptr<X509> loadX509FromFile( std::string filename ) {
+    FILE* f = fopen( filename.c_str(), "r" );
+
+    if( !f ) {
+        return std::shared_ptr<X509>();
+    }
+
+    X509* key = PEM_read_X509( f, NULL, NULL, 0 );
+    fclose( f );
+
+    if( !key ) {
+        return std::shared_ptr<X509>();
+    }
+
+    return std::shared_ptr<X509>(
+        key,
+        []( X509 * ref ) {
+            X509_free( ref );
+        } );
+}
+
+std::shared_ptr<EVP_PKEY> loadPkeyFromFile( std::string filename ) {
+    FILE* f = fopen( filename.c_str(), "r" );
+
+    if( !f ) {
+        return std::shared_ptr<EVP_PKEY>();
+    }
+
+    EVP_PKEY* key = PEM_read_PrivateKey( f, NULL, NULL, 0 );
+    fclose( f );
+
+    if( !key ) {
+        return std::shared_ptr<EVP_PKEY>();
+    }
+
+    return std::shared_ptr<EVP_PKEY>(
+        key,
+        []( EVP_PKEY * ref ) {
+            EVP_PKEY_free( ref );
+        } );
+}
+
+SimpleOpensslSigner::SimpleOpensslSigner() {
+    caCert = loadX509FromFile( profiles[0].cert );
+    caKey = loadPkeyFromFile( profiles[0].key );
+}
+
+SimpleOpensslSigner::~SimpleOpensslSigner() {
+}
+
+std::shared_ptr<BIGNUM> SimpleOpensslSigner::nextSerial( uint16_t profile ) {
+    std::ifstream serialif( "serial" );
+    std::string res;
+    serialif >> res;
+    serialif.close();
+
+    BIGNUM* bn = 0;
+
+    if( res == "" ) {
+        bn = BN_new();
+
+        if( !bn ) {
+            throw "Initing serial failed";
+        }
+    } else {
+        if( !BN_hex2bn( &bn, res.c_str() + 1 ) ) {
+            throw "Parsing serial failed.";
+        }
+    }
+
+    std::shared_ptr<BIGNUM> serial = std::shared_ptr<BIGNUM>( bn, BN_free );
+
+    std::shared_ptr<unsigned char> data = std::shared_ptr<unsigned char>( ( unsigned char* ) malloc( BN_num_bytes( serial.get() ) + 20 ), free );
+    int len = BN_bn2bin( serial.get(), data.get() );
+
+    data.get()[len] = 0x0;
+    data.get()[len + 1] = 0x0; // signer id
+
+    data.get()[len + 2] = profile >> 8;
+    data.get()[len + 3] = profile & 0xFF; // profile id
+
+    if( !RAND_bytes( data.get() + len + 4, 16 ) || !BN_add_word( serial.get(), 1 ) ) {
+        throw "Big number math failed while calcing serials.";
+    }
+
+    char* serStr = BN_bn2hex( serial.get() );
+    std::ofstream serialf( "serial" );
+    serialf << serStr;
+    serialf.close();
+    OPENSSL_free( serStr );
+
+    return std::shared_ptr<BIGNUM>( BN_bin2bn( data.get(), len + 4 + 16 , 0 ), BN_free );
+}
+
+std::shared_ptr<SignedCertificate> SimpleOpensslSigner::sign( std::shared_ptr<TBSCertificate> cert ) {
+    if( !caKey ) {
+        throw "CA-key not found";
+    }
+
+    std::shared_ptr<X509Req> req;
+
+    if( cert->csr_type == "SPKAC" ) {
+        req = X509Req::parseSPKAC( cert->csr_content );
+    } else if( cert->csr_type == "CSR" ) {
+        req = X509Req::parse( cert->csr_content );
+    } else {
+        throw "Error, unknown REQ rype " + ( cert->csr_type );
+    }
+
+    int i = req->verify();
+
+    if( i < 0 ) {
+        throw "Signature problems ... ";
+    } else if( i == 0 ) {
+        throw "Signature did not match";
+    } else {
+        std::cerr << "Signature ok" << std::endl;
+    }
+
+    // Construct the Certificate
+    X509Cert c = X509Cert();
+    std::shared_ptr<X509> retsh = std::shared_ptr<X509>( X509_new(), X509_free );
+    X509* ret = retsh.get();
+
+    if( !ret ) {
+        throw "Creating X509 failed.";
+    }
+
+    X509_NAME* subjectP = X509_NAME_new();
+
+    if( !subjectP ) {
+        throw "malloc failure";
+    }
+
+    for( std::shared_ptr<AVA> a : cert->AVAs ) {
+        if( a->name == "CN" ) {
+            c.addRDN( NID_commonName, a->value );
+        } else if( a->name == "EMAIL" ) {
+            c.addRDN( NID_pkcs9_emailAddress, a->value );
+        } else if( a->name == "C" ) {
+            c.addRDN( NID_countryName, a->value );
+        } else if( a->name == "L" ) {
+            c.addRDN( NID_localityName, a->value );
+        } else if( a->name == "ST" ) {
+            c.addRDN( NID_stateOrProvinceName, a->value );
+        } else if( a->name == "O" ) {
+            c.addRDN( NID_organizationName, a->value );
+        } else if( a->name == "OU" ) {
+            c.addRDN( NID_organizationalUnitName, a->value );
+        } else {
+            throw "unknown AVA-type";
+        }
+    }
+
+    c.setIssuerNameFrom( caCert );
+    c.setPubkeyFrom( req );
+    long int profile = strtol( cert->profile.c_str(), 0, 10 );
+
+    if( profile > 0xFFFF || profile < 0 || ( profile == 0 && cert->profile != "0" ) ) {
+        throw "invalid profile id";
+    }
+
+    std::shared_ptr<BIGNUM> ser = nextSerial( profile );
+    c.setSerialNumber( ser.get() );
+    c.setTimes( 0, 60 * 60 * 24 * 10 );
+    c.setExtensions( caCert, cert->SANs );
+
+    std::shared_ptr<SignedCertificate> output = c.sign( caKey, cert->md );
+
+    return output;
+}
diff --git a/src/simpleOpensslSigner.h b/src/simpleOpensslSigner.h
new file mode 100644 (file)
index 0000000..287f42c
--- /dev/null
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <openssl/ssl.h>
+
+#include "database.h"
+#include "signer.h"
+
+class SimpleOpensslSigner : public Signer {
+private:
+    static std::shared_ptr<int> lib_ref;
+    std::shared_ptr<EVP_PKEY> caKey;
+    std::shared_ptr<X509> caCert;
+    std::shared_ptr<BIGNUM> nextSerial( uint16_t profile );
+public:
+    SimpleOpensslSigner();
+    ~SimpleOpensslSigner();
+    std::shared_ptr<SignedCertificate> sign( std::shared_ptr<TBSCertificate> cert );
+};
index 4618c427d5cf296f1e2381abcd74d4dc99bf0132..a765a9a333d3888b2d566199c74d2e97daefd9b0 100644 (file)
@@ -1,7 +1,93 @@
+MKDIR = mkdir -p
+
+ifneq (,$(filter noopt,$(DEB_BUILD_OPTIONS)))
+    CFLAGS += -O0
+else
+    CFLAGS += -O2
+endif
+
+BIN="bin/cassiopeia-test"
+LIBS=openssl collissiondetect
+
+LT_CC=libtool --mode=compile gcc
+LT_CC_DEP=g++
+LT_CXX=libtool --mode=compile g++
+LT_CXX_DEP=g++
+LT_LD=libtool --mode=link g++
+
+CC=${LT_CC}
+CC_DEP=${LT_CC_DEP}
+CXX=${LT_CXX}
+CXX_DEP=${LT_CXX_DEP}
+LD=${LT_LD}
+
+ifneq (,$(filter debug,$(DEB_BUILD_OPTIONS)))
+ADDFLAGS=-DNO_DAEMON
+endif
+
+CFLAGS=-O3 -g -flto -Wall -Werror -Wextra -pedantic -std=c++11 ${ADDFLAGS}
+CXXFLAGS=$(CFLAGS)
+LDFLAGS=-O3 -g -flto -lmysqlclient -lssl -lcrypto -ldl -lboost_unit_test_framework
+
+SRC_DIR=src
+OBJ_DIR=obj
+DEP_DIR=dep
+
+FS_SRC=$(wildcard ${SRC_DIR}/*.cpp)
+FS_BIN=$(wildcard ${SRC_DIR}/app/*.cpp)
+FS_LIBS=$(wildcard lib/*/)
+FS_OBJ=$(FS_SRC:${SRC_DIR}/%.cpp=${OBJ_DIR}/%.lo)
+FS_DEP=$(FS_SRC:${SRC_DIR}/%.cpp=${DEP_DIR}/%.d)
+
+.SUFFIXES: .c .cpp .d
+
 .PHONY: all
-all:
-       @echo 'Performing some important tests ...'
+all: build
 
 .PHONY: clean
-clean:
-       @echo 'Cleaning up behind us ...'
+clean::
+       -rm -rf .libs
+       -rm -rf *.a
+       -rm -rf *.d
+       -rm -rf *.o
+       -rm -rf *.la
+       -rm -rf *.lo
+       -rm -rf *.so
+       -rm -rf ${OBJ_DIR}
+       -rm -rf ${DEP_DIR}
+
+build: cassiopeia-test
+       ${BIN}
+
+.PHONY: install
+install: build
+
+.PHONY: libs
+libs: ${LIBS}
+
+.PHONY: openssl
+openssl:
+       ${MAKE} -C ../lib/openssl
+
+.PHONY: collissiondetect
+collissiondetect:
+       ${MAKE} -C ../lib/collissiondetect
+
+# --------
+
+cassiopeia-test: bin/cassiopeia-test
+
+bin/cassiopeia-test: libs ${FS_OBJ}
+       ${MKDIR} $(shell dirname $@) && ${LT_LD} ${LDFLAGS} -o $@ ${FS_OBJ}
+
+${DEP_DIR}/%.d: ${SRC_DIR}/%.cpp
+       ${MKDIR} $(shell dirname $@) && $(CXX_DEP) $(CXXFLAGS) -M -MF $@ $<
+${DEP_DIR}/%.d: ${SRC_DIR}/%.c
+       ${MKDIR} $(shell dirname $@) && $(CC) $(CXXFLAGS) -M -MF $@ $<
+
+${OBJ_DIR}/%.lo ${OBJ_DIR}/%.o: ${SRC_DIR}/%.c ${DEP_DIR}/%.d
+       ${MKDIR} $(shell dirname $@) && $(CC) $(CFLAGS) -o $@ -c $<
+${OBJ_DIR}/%.lo ${OBJ_DIR}/%.o: ${SRC_DIR}/%.cpp ${DEP_DIR}/%.d
+       ${MKDIR} $(shell dirname $@) && $(CXX) $(CXXFLAGS) -o $@ -c $<
+
+-include $(FS_DEP)
diff --git a/test/src/main.cpp b/test/src/main.cpp
new file mode 100644 (file)
index 0000000..313b620
--- /dev/null
@@ -0,0 +1,4 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MODULE Cassiopeia
+
+#include <boost/test/unit_test.hpp>
diff --git a/test/src/sanity.cpp b/test/src/sanity.cpp
new file mode 100644 (file)
index 0000000..81851bb
--- /dev/null
@@ -0,0 +1,9 @@
+#define BOOST_TEST_NO_MAIN
+#define BOOST_TEST_MODULE Cassiopeia
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_CASE(Foo)
+{
+    BOOST_REQUIRE(true);
+}