Add public accessible PublicKeyData/PublicKeyScanner classes
[platform/upstream/libzypp.git] / zypp / PublicKey.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/PublicKey.cc
10  *
11 */
12 #include <climits>
13
14 #include <iostream>
15 #include <vector>
16
17 #include "zypp/base/Gettext.h"
18 #include "zypp/base/String.h"
19 #include "zypp/base/Regex.h"
20 #include "zypp/PublicKey.h"
21 #include "zypp/ExternalProgram.h"
22 #include "zypp/TmpPath.h"
23 #include "zypp/PathInfo.h"
24 #include "zypp/base/Exception.h"
25 #include "zypp/base/LogTools.h"
26 #include "zypp/Date.h"
27 #include "zypp/TmpPath.h"
28
29 #include <ctime>
30
31 using std::endl;
32
33 ///////////////////////////////////////////////////////////////////
34 namespace zypp
35 { /////////////////////////////////////////////////////////////////
36
37   ///////////////////////////////////////////////////////////////////
38   /// \class PublicKeyData::Impl
39   /// \brief  PublicKeyData implementation.
40   ///////////////////////////////////////////////////////////////////
41   struct PublicKeyData::Impl
42   {
43     std::string _id;
44     std::string _name;
45     std::string _fingerprint;
46     Date        _created;
47     Date        _expires;
48
49   public:
50     /** Offer default Impl. */
51     static shared_ptr<Impl> nullimpl()
52     {
53       static shared_ptr<Impl> _nullimpl( new Impl );
54       return _nullimpl;
55     }
56
57   private:
58     friend Impl * rwcowClone<Impl>( const Impl * rhs );
59     /** clone for RWCOW_pointer */
60     Impl * clone() const
61     { return new Impl( *this ); }
62   };
63   ///////////////////////////////////////////////////////////////////
64
65   ///////////////////////////////////////////////////////////////////
66   /// class PublicKeyData
67   ///////////////////////////////////////////////////////////////////
68
69   PublicKeyData::PublicKeyData()
70     : _pimpl( Impl::nullimpl() )
71   {}
72
73   PublicKeyData::~PublicKeyData()
74   {}
75
76   PublicKeyData::operator bool() const
77   { return !_pimpl->_fingerprint.empty(); }
78
79   std::string PublicKeyData::id() const
80   { return _pimpl->_id; }
81
82   std::string PublicKeyData::name() const
83   { return _pimpl->_name; }
84
85   std::string PublicKeyData::fingerprint() const
86   { return _pimpl->_fingerprint; }
87
88   Date PublicKeyData::created() const
89   { return _pimpl->_created; }
90
91   Date PublicKeyData::expires() const
92   { return _pimpl->_expires; }
93
94   bool PublicKeyData::expired() const
95   { return( _pimpl->_expires && _pimpl->_expires < Date::now() ); }
96
97   int PublicKeyData::daysToLive() const
98   {
99     if ( _pimpl->_expires )
100     {
101       Date exp( _pimpl->_expires - Date::now() );
102       return exp < 0 ? exp / Date::day - 1 : exp / Date::day;
103     }
104     return INT_MAX;
105   }
106
107   std::string PublicKeyData::expiresAsString() const
108   {
109     if ( !_pimpl->_expires )
110     { // translators: an annotation to a gpg keys expiry date
111       return _("(does not expire)");
112     }
113     std::string ret( _pimpl->_expires.asString() );
114     int ttl( daysToLive() );
115     if ( ttl <= 90 )
116     {
117       ret += " ";
118       if ( ttl < 0 )
119       { // translators: an annotation to a gpg keys expiry date
120         ret += _("(EXPIRED)");
121       }
122       else if ( ttl == 0 )
123       { // translators: an annotation to a gpg keys expiry date
124         ret += _("(expires within 24h)");
125       }
126       else
127       { // translators: an annotation to a gpg keys expiry date
128         ret += str::form( _PL("(expires in %d day)", "(expires in %d days)", ttl ), ttl );
129       }
130     }
131     return ret;
132   }
133
134   std::string PublicKeyData::gpgPubkeyVersion() const
135   { return _pimpl->_id.empty() ? _pimpl->_id : str::toLower( _pimpl->_id.substr(8,8) ); }
136
137   std::string PublicKeyData::gpgPubkeyRelease() const
138   { return _pimpl->_created ? str::hexstring( _pimpl->_created ).substr(2) : std::string(); }
139
140   std::string PublicKeyData::asString() const
141   {
142     return str::form( "[%s-%s] [%s] [%s] [TTL %d]",
143                       _pimpl->_id.c_str(),
144                       gpgPubkeyRelease().c_str(),
145                       _pimpl->_name.c_str(),
146                       _pimpl->_fingerprint.c_str(),
147                       daysToLive() );
148   }
149
150   std::ostream & dumpOn( std::ostream & str, const PublicKeyData & obj )
151   {
152     str << "[" << obj.name() << "]" << endl;
153     str << "  fpr " << obj.fingerprint() << endl;
154     str << "   id " << obj.id() << endl;
155     str << "  cre " << Date::ValueType(obj.created()) << ' ' << obj.created() << endl;
156     str << "  exp " << Date::ValueType(obj.expires()) << ' ' << obj.expiresAsString() << endl;
157     str << "  ttl " << obj.daysToLive() << endl;
158     str << "  rpm " << obj.gpgPubkeyVersion() << "-" << obj.gpgPubkeyRelease() << endl;
159     str << "]";
160     return str;
161   }
162
163   bool operator==( const PublicKeyData & lhs, const PublicKeyData & rhs )
164   { return ( lhs.fingerprint() == rhs.fingerprint() && lhs.created() == rhs.created() ); }
165
166
167   ///////////////////////////////////////////////////////////////////
168   /// \class PublicKeyScanner::Impl
169   /// \brief  PublicKeyScanner implementation.
170   ///////////////////////////////////////////////////////////////////
171   struct PublicKeyScanner::Impl
172   {
173     std::vector<std::string>                    _words;
174     enum { pNONE, pPUB, pSIG, pFPR, pUID }      _parseEntry;
175
176    Impl()
177       : _parseEntry( pNONE )
178     {}
179
180     void scan( std::string & line_r, std::list<PublicKeyData> & keys_r )
181     {
182       // pub:-:1024:17:A84EDAE89C800ACA:971961473:1214043198::-:SuSE Package Signing Key <build@suse.de>:
183       // fpr:::::::::79C179B2E1C820C1890F9994A84EDAE89C800ACA:
184       // sig:::17:A84EDAE89C800ACA:1087899198:::::[selfsig]::13x:
185       // sig:::17:9E40E310000AABA4:980442706::::[User ID not found]:10x:
186       // sig:::1:77B2E6003D25D3D9:980443247::::[User ID not found]:10x:
187       // sig:::17:A84EDAE89C800ACA:1318348291:::::[selfsig]::13x:
188       // sub:-:2048:16:197448E88495160C:971961490:1214043258::: [expires: 2008-06-21]
189       // sig:::17:A84EDAE89C800ACA:1087899258:::::[keybind]::18x:
190       if ( line_r.empty() )
191         return;
192
193       // quick check for interesting entries
194       _parseEntry = pNONE;
195       switch ( line_r[0] )
196       {
197         #define DOTEST( C1, C2, C3, E ) case C1: if ( line_r[1] == C2 && line_r[2] == C3 && line_r[3] == ':' ) _parseEntry = E; break
198         DOTEST( 'p', 'u', 'b', pPUB );
199         DOTEST( 's', 'i', 'g', pSIG );
200         DOTEST( 'f', 'p', 'r', pFPR );
201         DOTEST( 'u', 'i', 'd', pUID );
202         #undef DOTEST
203       }
204       if ( _parseEntry == pNONE )
205         return;
206
207       if ( line_r[line_r.size()-1] == '\n' )
208         line_r.erase( line_r.size()-1 );
209       // DBG << line_r << endl;
210
211       _words.clear();
212       str::splitFields( line_r, std::back_inserter(_words), ":" );
213
214       PublicKeyData * key( &keys_r.back() );
215
216       switch ( _parseEntry )
217       {
218         case pPUB:
219           keys_r.push_back( PublicKeyData() );  // reset upon new key
220           key = &keys_r.back();
221           key->_pimpl->_id      = _words[4];
222           key->_pimpl->_name    = str::replaceAll( _words[9], "\\x3a", ":" );
223           key->_pimpl->_created = Date(str::strtonum<Date::ValueType>(_words[5]));
224           key->_pimpl->_expires = Date(str::strtonum<Date::ValueType>(_words[6]));
225           break;
226
227         case pSIG:
228           // Update creation/modification date from signatures type "13x".
229           if ( _words[_words.size()-2] == "13x" )
230           {
231             Date cdate(str::strtonum<Date::ValueType>(_words[5]));
232             if ( key->_pimpl->_created < cdate )
233               key->_pimpl->_created = cdate;
234           }
235           break;
236
237         case pFPR:
238           if ( key->_pimpl->_fingerprint.empty() )
239             key->_pimpl->_fingerprint = _words[9];
240           break;
241
242         case pUID:
243           if ( ! _words[9].empty() )
244             key->_pimpl->_name = str::replaceAll( _words[9], "\\x3a", ":" );
245           break;
246
247         case pNONE:
248           break;
249       }
250     }
251   };
252   ///////////////////////////////////////////////////////////////////
253
254   ///////////////////////////////////////////////////////////////////
255   // class PublicKeyScanner
256   ///////////////////////////////////////////////////////////////////
257
258   PublicKeyScanner::PublicKeyScanner()
259     : _pimpl( new Impl )
260   {}
261
262   PublicKeyScanner::~PublicKeyScanner()
263   {}
264
265   void PublicKeyScanner::scan( std::string line_r )
266   { _pimpl->scan( line_r, _keys ); }
267
268
269   ///////////////////////////////////////////////////////////////////
270   /// \class PublicKey::Impl
271   /// \brief  PublicKey implementation.
272   ///////////////////////////////////////////////////////////////////
273   struct PublicKey::Impl
274   {
275     Impl()
276     {}
277
278     Impl( const Pathname & keyFile_r )
279     {
280       PathInfo info( keyFile_r );
281       MIL << "Taking pubkey from " << keyFile_r << " of size " << info.size() << " and sha1 " << filesystem::checksum(keyFile_r, "sha1") << endl;
282
283       if ( !info.isExist() )
284         ZYPP_THROW(Exception("Can't read public key from " + keyFile_r.asString() + ", file not found"));
285
286       if ( filesystem::hardlinkCopy( keyFile_r, _dataFile.path() ) != 0 )
287         ZYPP_THROW(Exception("Can't copy public key data from " + keyFile_r.asString() + " to " +  _dataFile.path().asString() ));
288
289       readFromFile();
290     }
291
292     Impl( const filesystem::TmpFile & sharedFile_r )
293       : _dataFile( sharedFile_r )
294     { readFromFile(); }
295
296     Impl( const filesystem::TmpFile & sharedFile_r, const PublicKeyData & keyData_r )
297       : _dataFile( sharedFile_r )
298       , _keyData( keyData_r )
299     {
300       if ( ! keyData_r )
301       {
302         WAR << "Invalid PublicKeyData supplied: scanning from file" << endl;
303         readFromFile();
304       }
305     }
306
307     public:
308       const PublicKeyData & keyData() const
309       { return _keyData; }
310
311       Pathname path() const
312       { return _dataFile.path(); }
313
314     protected:
315       void readFromFile()
316       {
317         PathInfo info( _dataFile.path() );
318         MIL << "Reading pubkey from " << info.path() << " of size " << info.size() << " and sha1 " << filesystem::checksum(info.path(), "sha1") << endl;
319
320         static filesystem::TmpDir dir;
321         const char* argv[] =
322         {
323           "gpg",
324           "-v",
325           "--no-default-keyring",
326           "--fixed-list-mode",
327           "--with-fingerprint",
328           "--with-colons",
329           "--homedir",
330           dir.path().asString().c_str(),
331           "--quiet",
332           "--no-tty",
333           "--no-greeting",
334           "--batch",
335           "--status-fd",
336           "1",
337           _dataFile.path().asString().c_str(),
338           NULL
339         };
340         ExternalProgram prog( argv, ExternalProgram::Discard_Stderr, false, -1, true );
341
342         PublicKeyScanner scanner;
343         for ( std::string line = prog.receiveLine(); !line.empty(); line = prog.receiveLine() )
344         {
345           scanner.scan( line );
346         }
347         prog.close();
348
349         switch ( scanner._keys.size() )
350         {
351           case 0:
352             ZYPP_THROW( BadKeyException( "File " + _dataFile.path().asString() + " doesn't contain public key data" , _dataFile.path() ) );
353             break;
354
355           case 1:
356             // ok.
357             break;
358
359           default:
360             WAR << "File " << _dataFile.path().asString() << " contains multiple keys: " <<  scanner._keys << endl;
361             break;
362         }
363
364         _keyData = scanner._keys.back();
365         MIL << "Read pubkey from " << info.path() << ": " << _keyData << endl;
366       }
367
368     private:
369       filesystem::TmpFile       _dataFile;
370       PublicKeyData             _keyData;
371
372     public:
373       /** Offer default Impl. */
374       static shared_ptr<Impl> nullimpl()
375       {
376         static shared_ptr<Impl> _nullimpl( new Impl );
377         return _nullimpl;
378       }
379
380     private:
381       friend Impl * rwcowClone<Impl>( const Impl * rhs );
382       /** clone for RWCOW_pointer */
383       Impl * clone() const
384       { return new Impl( *this ); }
385   };
386   ///////////////////////////////////////////////////////////////////
387
388   ///////////////////////////////////////////////////////////////////
389   // class PublicKey
390   ///////////////////////////////////////////////////////////////////
391   PublicKey::PublicKey()
392   : _pimpl( Impl::nullimpl() )
393   {}
394
395   PublicKey::PublicKey( const Pathname & file )
396   : _pimpl( new Impl( file ) )
397   {}
398
399   PublicKey::PublicKey( const filesystem::TmpFile & sharedfile )
400   : _pimpl( new Impl( sharedfile ) )
401   {}
402
403   PublicKey::PublicKey( const filesystem::TmpFile & sharedfile, const PublicKeyData & keydata )
404   : _pimpl( new Impl( sharedfile, keydata ) )
405   {}
406
407   PublicKey::~PublicKey()
408   {}
409
410   const PublicKeyData & PublicKey::keyData() const
411   { return _pimpl->keyData(); }
412
413   Pathname PublicKey::path() const
414   { return _pimpl->path(); }
415
416   std::string PublicKey::id() const
417   { return keyData().id(); }
418
419   std::string PublicKey::name() const
420   { return keyData().name(); }
421
422   std::string PublicKey::fingerprint() const
423   { return keyData().fingerprint(); }
424
425   Date PublicKey::created() const
426   { return keyData().created(); }
427
428   Date PublicKey::expires() const
429   { return keyData().expires(); }
430
431   bool PublicKey::expired() const
432   { return keyData().expired(); }
433
434   int PublicKey::daysToLive() const
435   { return keyData().daysToLive(); }
436
437   std::string PublicKey::expiresAsString() const
438   { return keyData().expiresAsString(); }
439
440   std::string PublicKey::gpgPubkeyVersion() const
441   { return keyData().gpgPubkeyVersion(); }
442
443   std::string PublicKey::gpgPubkeyRelease() const
444   { return keyData().gpgPubkeyRelease(); }
445
446   std::string PublicKey::asString() const
447   { return keyData().asString(); }
448
449   bool PublicKey::operator==( PublicKey rhs ) const
450   { return rhs.keyData() == keyData(); }
451
452   bool PublicKey::operator==( std::string sid ) const
453   { return sid == id(); }
454
455   std::ostream & dumpOn( std::ostream & str, const PublicKey & obj )
456   { return dumpOn( str, obj.keyData() ); }
457
458   /////////////////////////////////////////////////////////////////
459 } // namespace zypp
460 ///////////////////////////////////////////////////////////////////