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