bin
dep
obj
+
+# Emacs
+*~
+\#*#
+
+# Devel key files
+*.crt
+*.key
+*.csr
+config.txt
+serial
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
.PHONY: install
install: build
${INSTALL_PROGRAM} bin/cassiopeia ${DESTDIR}/usr/bin/cassiopeia
+ ${INSTALL_DIR} ${DESTDIR}/etc/cacert/cassiopeia
.PHONY: libs
libs: ${LIBS}
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 $@ $<
--- /dev/null
+/files
+/cacert-cassiopeia
+/cacert-cassiopeia-doc
+/*.substvars
+/*.debhelper
+++ /dev/null
-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
--- /dev/null
+START_DAEMON=0
--- /dev/null
+#!/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
+:
--- /dev/null
+/usr/bin/cassiopeia
+/etc/cacert/cassiopeia
--- /dev/null
+debian/cassiopeia.1
--- /dev/null
+.\" 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.
+++ /dev/null
-/usr/bin/cassiopeia
-cassiopeia (0.1) unstable; urgency=low
+cacert-cassiopeia (0.1) unstable; urgency=low
* Initial Release.
-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
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/*
.
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.
--- /dev/null
+#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;
+}
--- /dev/null
+#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 );
+};
--- /dev/null
+#include "database.h"
--- /dev/null
+#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;
+};
-/*
- 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;
+ }
+ }
}
--- /dev/null
+#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";
+ }
+}
--- /dev/null
+#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 );
+};
--- /dev/null
+#pragma once
+
+#include <memory>
+
+#include "database.h"
+
+class Signer {
+public:
+ virtual std::shared_ptr<SignedCertificate> sign( std::shared_ptr<TBSCertificate> cert ) = 0;
+};
--- /dev/null
+#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;
+}
--- /dev/null
+#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 );
+};
+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)
--- /dev/null
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MODULE Cassiopeia
+
+#include <boost/test/unit_test.hpp>
--- /dev/null
+#define BOOST_TEST_NO_MAIN
+#define BOOST_TEST_MODULE Cassiopeia
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_CASE(Foo)
+{
+ BOOST_REQUIRE(true);
+}