]> WPIA git - cassiopeia.git/blob - src/apps/client.cpp
chg: make cassiopeia conform to db schema version 33
[cassiopeia.git] / src / apps / client.cpp
1 #include <sys/stat.h>
2 #include <unistd.h>
3
4 #include <iostream>
5 #include <fstream>
6 #include <streambuf>
7 #include <unordered_map>
8
9 #include "db/database.h"
10 #include "db/psql.h"
11 #include "crypto/simpleOpensslSigner.h"
12 #include "crypto/remoteSigner.h"
13 #include "crypto/sslUtil.h"
14 #include "log/logger.hpp"
15 #include "util.h"
16 #include "io/bios.h"
17 #include "io/slipBio.h"
18 #include "config.h"
19 #include <internal/bio.h>
20 #include <dirent.h>
21 #include <crypto/X509.h>
22
23 #ifdef NO_DAEMON
24 #define DAEMON false
25 #else
26 #define DAEMON true
27 #endif
28
29 extern std::string keyDir;
30 extern std::string sqlHost, sqlUser, sqlPass, sqlDB;
31 extern std::string serialPath;
32 extern std::unordered_map<std::string, std::shared_ptr<CAConfig>> CAs;
33
34 void checkCRLs( std::shared_ptr<Signer> sign ) {
35
36     logger::note( "Signing CRLs" );
37
38     for( auto& x : CAs ) {
39         if( !x.second->crlNeedsResign() ) {
40             continue;
41         }
42
43         logger::notef( "Resigning CRL: %s ...", x.second->name );
44
45         try {
46             std::vector<std::string> serials;
47             std::pair<std::shared_ptr<CRL>, std::string> rev = sign->revoke( x.second, serials );
48         } catch( const std::exception& e ) {
49             logger::error( "Exception: ", e.what() );
50         }
51     }
52 }
53
54 bool pathExists( const std::string& name ) {
55     struct stat buffer;
56     return stat( name.c_str(), &buffer ) == 0;
57 }
58
59 void signOCSP( std::shared_ptr<Signer> sign, std::string profileName, std::string req, std::string crtName, std::string failName ) {
60     auto cert = std::make_shared<TBSCertificate>();
61     cert->ocspCA = profileName;
62     cert->wishFrom = "now";
63     cert->wishTo = "1y";
64     cert->md = "sha512";
65
66     logger::note( "INFO: Message Digest: ", cert->md );
67
68     cert->csr_content = req;
69     cert->csr_type = "CSR";
70     auto nAVA = std::make_shared<AVA>();
71     nAVA->name = "CN";
72     nAVA->value = "OCSP Responder";
73     cert->AVAs.push_back( nAVA );
74
75     std::shared_ptr<SignedCertificate> res = sign->sign( cert );
76
77     if( !res ) {
78         writeFile( failName, "failed" );
79         logger::error( "OCSP Cert signing failed." );
80         return;
81     }
82
83     writeFile( crtName, res->certificate );
84     logger::notef( "Cert log: %s", res->log );
85 }
86
87 void checkOCSP( std::shared_ptr<Signer> sign ) {
88     std::unique_ptr<DIR, std::function<void( DIR * )>> dp( opendir( "ca" ), []( DIR * d ) {
89         closedir( d );
90     } );
91
92     // When opendir fails and returns 0 the unique_ptr will be considered unintialized and will not call closedir.
93     // Even if closedir would be called, according to POSIX it MAY handle nullptr properly (for glibc it does).
94     if( !dp ) {
95         logger::error( "CA directory not found" );
96         return;
97     }
98
99     struct dirent *ep;
100
101     while( ( ep = readdir( dp.get() ) ) ) {
102         if( ep->d_name[0] == '.' ) {
103             continue;
104         }
105
106         std::string profileName( ep->d_name );
107         std::string csr = "ca/" + profileName + "/ocsp.csr";
108
109         if( ! pathExists( csr ) ) {
110             continue;
111         }
112
113         std::string crtName = "ca/" + profileName + "/ocsp.crt";
114
115         if( pathExists( crtName ) ) {
116             continue;
117         }
118
119         std::string failName = "ca/" + profileName + "/ocsp.fail";
120
121         if( pathExists( failName ) ) {
122             continue;
123         }
124
125         logger::notef( "Discovered OCSP CSR that needs action: %s", csr );
126         std::string req = readFile( csr );
127         std::shared_ptr<X509Req> parsed = X509Req::parseCSR( req );
128
129         if( parsed->verify() <= 0 ) {
130             logger::errorf( "Invalid CSR for %s", profileName );
131             continue;
132         }
133
134         signOCSP( sign, profileName, req, crtName, failName );
135     }
136 }
137
138
139 int main( int argc, const char *argv[] ) {
140     bool once = false;
141     bool resetOnly = false;
142
143     if( argc == 2 && std::string( "--once" ) == argv[1] ) {
144         once = true;
145     }
146
147     if( argc == 2 && std::string( "--reset" ) == argv[1] ) {
148         resetOnly = true;
149     }
150
151     std::string path;
152
153 #ifdef NDEBUG
154     path = "/etc/wpia/cassiopeia/cassiopeia.conf";
155 #else
156     path = "config.txt";
157 #endif
158
159     if( parseConfig( path ) != 0 ) {
160         logger::fatal( "Error: Could not parse the configuration file." );
161         return -1;
162     }
163
164     if( serialPath == "" ) {
165         logger::fatal( "Error: no serial device is given!" );
166         return -1;
167     }
168
169     std::shared_ptr<JobProvider> jp = std::make_shared<PostgresJobProvider>( sqlHost, sqlUser, sqlPass, sqlDB );
170     std::shared_ptr<BIO> b = openSerial( serialPath );
171     std::shared_ptr<BIO_METHOD> m( toBio<SlipBIO>(), BIO_meth_free );
172     std::shared_ptr<BIO> slip1( BIO_new( m.get() ), BIO_free );
173     static_cast<SlipBIO *>( slip1->ptr )->setTarget( std::make_shared<OpensslBIOWrapper>( b ), false );
174     auto sign = std::make_shared<RemoteSigner>( slip1, generateSSLContext( false ) );
175     // std::shared_ptr<Signer> sign( new SimpleOpensslSigner() );
176
177     if( resetOnly ) {
178         std::cout << "Doing BIO reset" << std::endl;
179         int result = BIO_reset( slip1.get() );
180         std::cout << "Did BIO reset, result " << result << ", exiting." << std::endl;
181         return result;
182     }
183
184     time_t lastCRLCheck = 0;
185
186     while( true ) {
187         try {
188             time_t current;
189             time( &current );
190
191             if( lastCRLCheck + 30 * 60 < current ) {
192                 // todo set good log TODO FIXME
193                 auto ostreamFree = []( std::ostream * o ) {
194                     ( void ) o;
195                 };
196                 sign->setLog( std::shared_ptr<std::ostream>( &std::cout, ostreamFree ) );
197                 checkCRLs( sign );
198                 lastCRLCheck = current;
199             }
200
201             checkOCSP( sign );
202
203             std::shared_ptr<Job> job;
204
205             try {
206                 job = jp->fetchJob();
207             } catch( std::exception& e ) {
208                 logger::errorf( "Exception while fetchJob: %s", e.what() );
209             }
210
211             if( !job ) {
212                 sleep( 5 );
213                 continue;
214             }
215
216             std::shared_ptr<std::ofstream> logPtr = openLogfile( std::string( "logs/" ) + job->id + std::string( "_" ) + job->warning + std::string( ".log" ) );
217
218             logger::logger_set log_set( {logger::log_target( *logPtr, logger::level::debug )}, logger::auto_register::on );
219
220             logger::note( "TASK ID: ", job->id );
221             logger::note( "TRY:     ", job->warning );
222             logger::note( "TARGET:  ", job->target );
223             logger::note( "TASK:    ", job->task );
224
225             if( job->task == "sign" ) {
226                 try {
227                     std::shared_ptr<TBSCertificate> cert = jp->fetchTBSCert( job );
228
229                     if( !cert ) {
230                         logger::error( "Unable to load CSR" );
231                         jp->failJob( job );
232                         continue;
233                     }
234
235                     cert->wishFrom = job->from;
236                     cert->wishTo = job->to;
237                     logger::note( "INFO: Message Digest: ", cert->md );
238                     logger::note( "INFO: Profile ID: ", cert->profile );
239
240                     for( auto& SAN : cert->SANs ) {
241                         logger::notef( "INFO: SAN %s: %s", SAN->type, SAN->content );
242                     }
243
244                     for( auto& AVA : cert->AVAs ) {
245                         logger::notef( "INFO: AVA %s: %s", AVA->name, AVA->value );
246                     }
247
248                     logger::note( "FINE: CSR content:\n", cert->csr_content );
249
250                     std::shared_ptr<SignedCertificate> res = sign->sign( cert );
251
252                     if( !res ) {
253                         logger::error( "ERROR: The signer failed. No certificate was returned." );
254                         jp->failJob( job );
255                         continue;
256                     }
257
258                     logger::note( "FINE: CERTIFICATE LOG:\n", res->log,
259                                   "FINE: CERTIFICATE:\n", res->certificate );
260
261                     jp->writeBack( job, res ); //! \FIXME: Check return value
262                     logger::note( "FINE: signing done." );
263
264                     if( DAEMON ) {
265                         jp->finishJob( job );
266                     }
267
268                     continue;
269                 } catch( std::exception& c ) {
270                     logger::error( "ERROR: ", c.what() );
271                 }
272
273                 try {
274                     jp->failJob( job );
275                 } catch( std::exception& c ) {
276                     logger::error( "ERROR: ", c.what() );
277                 }
278             } else if( job->task == "revoke" ) {
279                 try {
280                     logger::note( "revoking" );
281                     auto data = jp->getRevocationInfo( job );
282                     std::vector<std::string> serials;
283                     serials.push_back( data.first );
284                     logger::note( "revoking" );
285                     std::pair<std::shared_ptr<CRL>, std::string> rev = sign->revoke( CAs.at( data.second ), serials );
286                     std::string date = rev.second;
287                     const unsigned char *pos = ( const unsigned char * ) date.data();
288                     std::shared_ptr<ASN1_TIME> time( d2i_ASN1_TIME( NULL, &pos, date.size() ), ASN1_TIME_free );
289
290                     jp->writeBackRevocation( job, timeToString( time ) );
291                     jp->finishJob( job );
292                 } catch( const std::exception& c ) {
293                     logger::error( "Exception: ", c.what() );
294                 }
295             } else {
296                 logger::errorf( "Unknown job type (\"%s\")", job->task );
297                 jp->failJob( job );
298             }
299
300             if( !DAEMON || once ) {
301                 return 0;
302             }
303         } catch( std::exception& e ) {
304             logger::errorf( "std::exception in mainloop: %s", e.what() );
305         }
306
307     }
308 }