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