Framework for looking up contacts directly in nepomuk in addition to going through...
[kdepim.git] / libkpgp / kpgpbase5.cpp
blob55db4783d5da52144ee2b749cad2a029e670c3fa
1 /*
2 kpgpbase5.cpp
4 Copyright (C) 2001,2002 the KPGP authors
5 See file AUTHORS.kpgp for details
7 This file is part of KPGP, the KDE PGP/GnuPG support library.
9 KPGP is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software Foundation,
16 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
20 #include "kpgpbase.h"
21 #include "kpgp.h"
23 #include <string.h> /* strncmp */
24 #include <assert.h>
26 #include <QRegExp>
27 #include <QDateTime>
29 #include <klocale.h>
30 #include <kshell.h>
31 #include <kdebug.h>
33 #include <algorithm>
36 namespace Kpgp {
38 Base5::Base5()
39 : Base()
44 Base5::~Base5()
49 int
50 Base5::encrypt( Block& block, const KeyIDList& recipients )
52 return encsign( block, recipients, 0 );
56 int
57 Base5::clearsign( Block& block, const char *passphrase )
59 return encsign( block, KeyIDList(), passphrase );
63 int
64 Base5::encsign( Block& block, const KeyIDList& recipients,
65 const char *passphrase )
67 QByteArray cmd;
68 int exitStatus = 0;
69 int index;
70 // used to work around a bug in pgp5. pgp5 treats files
71 // with non ascii chars (umlauts, etc...) as binary files, but
72 // we want a clear signature
73 bool signonly = false;
75 if(!recipients.isEmpty() && passphrase != 0)
76 cmd = "pgpe +batchmode -afts ";
77 else if(!recipients.isEmpty())
78 cmd = "pgpe +batchmode -aft ";
79 else if(passphrase != 0)
81 cmd = "pgps +batchmode -abft ";
82 signonly = true;
84 else
86 errMsg = i18n("Neither recipients nor passphrase specified.");
87 return OK;
90 if(passphrase != 0)
91 cmd += addUserId();
93 if(!recipients.isEmpty())
95 if(Module::getKpgp()->encryptToSelf())
97 cmd += " -r 0x";
98 cmd += Module::getKpgp()->user();
100 KeyIDList::ConstIterator end( recipients.constEnd() );
102 for( KeyIDList::ConstIterator it = recipients.constBegin();
103 it != end; ++it ) {
104 cmd += " -r 0x";
105 cmd += (*it);
109 clear();
110 input = block.text();
112 if (signonly)
114 input.append("\n");
115 //input.replace(QRegExp("[ \t]+\n"), "\n"); //strip trailing whitespace
116 input = input.trimmed(); // PORT: check if that change was ok!
118 //We have to do this otherwise it's all in vain
120 exitStatus = run(cmd.data(), passphrase);
121 block.setError( error );
123 if(exitStatus != 0)
124 status = ERROR;
126 // now parse the returned info
127 if(error.contains("Cannot unlock private key") )
129 errMsg = i18n("The passphrase you entered is invalid.");
130 status |= ERROR;
131 status |= BADPHRASE;
133 //if(!ignoreUntrusted)
135 QByteArray aStr;
136 index = -1;
137 while((index = error.indexOf("WARNING: The above key",index+1)) != -1 )
139 int index2 = error.indexOf("But you previously",index);
140 int index3 = error.indexOf("WARNING: The above key",index+1);
141 if(index2 == -1 || (index2 > index3 && index3 != -1))
143 // the key wasn't valid, no encryption to this person
144 // extract the person
145 index2 = error.indexOf('\n',index);
146 index3 = error.indexOf('\n',index2+1);
147 aStr += error.mid(index2+1, index3-index2-1);
148 aStr += ", ";
151 if(!aStr.isEmpty())
153 aStr.truncate(aStr.length()-2);
154 if(error.contains("No valid keys found") )
155 errMsg = i18n("The key(s) you want to encrypt your message "
156 "to are not trusted. No encryption done.");
157 else
158 errMsg = i18n("The following key(s) are not trusted:\n%1\n"
159 "Their owner(s) will not be able to decrypt the message.",
160 QString::fromLocal8Bit( aStr ));
161 status |= ERROR;
162 status |= BADKEYS;
165 if((index = error.indexOf("No encryption keys found for")) != -1 )
167 index = error.indexOf(':',index);
168 int index2 = error.indexOf('\n',index);
170 errMsg = i18n("Missing encryption key(s) for:\n%1",
171 QString::fromLocal8Bit(error.mid(index,index2-index)));
172 // errMsg = QString("Missing encryption key(s) for: %1")
173 // .arg(error.mid(index,index2-index));
174 status |= ERROR;
175 status |= MISSINGKEY;
178 if(signonly) {
179 // dash-escape the input
180 if (input[0] == '-')
181 input = "- " + input;
182 for ( int idx = 0 ; (idx = input.indexOf("\n-", idx)) != -1 ; idx += 4 )
183 input.replace(idx, 2, "\n- -");
185 output = "-----BEGIN PGP SIGNED MESSAGE-----\n\n" + input + '\n' + output;
188 block.setProcessedText( output );
189 block.setStatus( status );
190 return status;
195 Base5::decrypt( Block& block, const char *passphrase )
197 clear();
198 input = block.text();
199 int exitStatus = run("pgpv -f +batchmode=1", passphrase);
200 if( !output.isEmpty() )
201 block.setProcessedText( output );
202 block.setError( error );
204 if(exitStatus == -1) {
205 errMsg = i18n("Error running PGP");
206 status = ERROR;
207 block.setStatus( status );
208 return status;
211 // lets parse the returned information.
212 int index = error.indexOf("Cannot decrypt message");
213 if(index != -1)
215 //kDebug( 5326 ) <<"message is encrypted";
216 status |= ENCRYPTED;
218 // ok. we have an encrypted message. Is the passphrase bad,
219 // or do we not have the secret key?
220 if(error.contains("Need a pass phrase") )
222 if(passphrase != 0)
224 errMsg = i18n("Bad passphrase; could not decrypt.");
225 kDebug( 5326 ) <<"Base: passphrase is bad";
226 status |= BADPHRASE;
227 status |= ERROR;
230 else
232 // we don't have the secret key
233 status |= NO_SEC_KEY;
234 status |= ERROR;
235 errMsg = i18n("You do not have the secret key needed to decrypt this message.");
236 kDebug( 5326 ) <<"Base: no secret key for this message";
238 // check for persons
239 #if 0
240 // ##### FIXME: This information is anyway currently not used
241 // I'll change it to always determine the recipients.
242 index = error.indexOf("can only be decrypted by:");
243 if(index != -1)
245 index = error.indexOf('\n',index);
246 int end = error.indexOf("\n\n",index);
248 mRecipients.clear();
249 int index2;
250 while( (index2 = error.indexOf('\n',index+1)) <= end )
252 QByteArray item = error.mid(index+1,index2-index-1);
253 item.trimmed();
254 mRecipients.append(item);
255 index = index2;
258 #endif
260 index = error.indexOf("Good signature");
261 if(index != -1)
263 //kDebug( 5326 ) <<"good signature";
264 status |= SIGNED;
265 status |= GOODSIG;
267 // get key ID of signer
268 index = error.indexOf("Key ID ", index) + 7;
269 block.setSignatureKeyId( error.mid(index, 8) );
271 // get signer
272 index = error.indexOf('"',index) + 1;
273 int index2 = error.indexOf('"', index);
274 block.setSignatureUserId( error.mid(index, index2-index) );
276 /// ### FIXME get signature date
277 block.setSignatureDate( "" );
279 index = error.indexOf("BAD signature");
280 if(index != -1)
282 //kDebug( 5326 ) <<"BAD signature";
283 status |= SIGNED;
284 status |= ERROR;
286 // get key ID of signer
287 index = error.indexOf("Key ID ", index) + 7;
288 block.setSignatureKeyId( error.mid(index, 8) );
290 // get signer
291 index = error.indexOf('"',index) + 1;
292 int index2 = error.indexOf('"', index);
293 block.setSignatureUserId( error.mid(index, index2-index) );
295 /// ### FIXME get signature date
296 block.setSignatureDate( "" );
298 index = error.indexOf("Signature by unknown key");
299 if(index != -1)
301 index = error.indexOf("keyid: 0x",index) + 9;
302 block.setSignatureKeyId( error.mid(index, 8) );
303 block.setSignatureUserId( QString() );
304 // FIXME: not a very good solution...
305 status |= SIGNED;
306 status |= GOODSIG;
308 /// ### FIXME get signature date
309 block.setSignatureDate( "" );
312 //kDebug( 5326 ) <<"status =" << status;
313 block.setStatus( status );
314 return status;
318 Key*
319 Base5::readPublicKey( const KeyID& keyId, const bool readTrust, Key* key )
321 status = 0;
322 int exitStatus = run( "pgpk -ll 0x" + keyId, 0, true );
324 if(exitStatus != 0) {
325 status = ERROR;
326 return 0;
329 key = parseSingleKey( output, key );
331 if( key == 0 )
333 return 0;
336 if( readTrust )
338 exitStatus = run( "pgpk -c 0x" + keyId, 0, true );
340 if(exitStatus != 0) {
341 status = ERROR;
342 return 0;
345 parseTrustDataForKey( key, output );
348 return key;
352 KeyList
353 Base5::publicKeys( const QStringList & patterns )
355 int exitStatus = 0;
357 QByteArray cmd = "pgpk -ll";
359 QStringList::ConstIterator end( patterns.end() );
362 for ( QStringList::ConstIterator it = patterns.constBegin();
363 it != end; ++it ) {
364 cmd += ' ';
365 cmd += KShell::quoteArg( *it ).toLocal8Bit();
367 status = 0;
368 exitStatus = run( cmd, 0, true );
370 if(exitStatus != 0) {
371 status = ERROR;
372 return KeyList();
375 // now we need to parse the output for public keys
376 KeyList keys = parseKeyList( output, false );
378 // sort the list of public keys
379 std::sort( keys.begin(), keys.end(), KeyCompare );
381 return keys;
385 KeyList
386 Base5::secretKeys( const QStringList & patterns )
388 QByteArray cmd = "pgpk -ll";
389 QStringList::ConstIterator end( patterns.constEnd() );
390 for ( QStringList::ConstIterator it = patterns.constBegin();
391 it != end; ++it ) {
392 cmd += ' ';
393 cmd += KShell::quoteArg( *it ).toLocal8Bit();
395 status = 0;
396 int exitStatus = run( cmd, 0, true );
398 if(exitStatus != 0) {
399 status = ERROR;
400 return KeyList();
403 // now we need to parse the output for secret keys
404 KeyList keys = parseKeyList( output, true );
406 // sort the list of public keys
407 std::sort( keys.begin(), keys.end(), KeyCompare );
409 return keys;
413 QByteArray Base5::getAsciiPublicKey(const KeyID& keyID)
415 if (keyID.isEmpty())
416 return QByteArray();
418 status = 0;
419 int exitStatus = run( "pgpk -xa 0x" + keyID, 0, true );
421 if(exitStatus != 0) {
422 status = ERROR;
423 return QByteArray();
426 return output;
431 Base5::signKey(const KeyID& keyID, const char *passphrase)
433 if(passphrase == 0) return false;
434 QByteArray cmd;
436 cmd = "pgpk -s -f +batchmode=1 0x";
437 cmd += keyID;
438 cmd += addUserId();
440 status = 0;
441 int exitStatus = run(cmd.data(), passphrase);
443 if (exitStatus != 0)
444 status = ERROR;
446 return status;
449 //-- private functions --------------------------------------------------------
451 Key*
452 Base5::parseKeyData( const QByteArray& output, int& offset, Key* key /* = 0 */ )
453 // This function parses the data for a single key which is output by PGP 5
454 // with the following command line:
455 // pgpk -ll
456 // It expects the key data to start at offset and returns the start of
457 // the next key's data in offset.
459 if( ( strncmp( output.data() + offset, "pub", 3 ) != 0 ) &&
460 ( strncmp( output.data() + offset, "sec", 3 ) != 0 ) )
462 kDebug( 5326 ) <<"Unknown key type or corrupt key data.";
463 return 0;
466 if( key == 0 )
467 key = new Key();
468 else
469 key->clear();
471 Subkey *subkey = 0;
472 bool primaryKey = true;
474 while( true )
476 int eol;
478 // search the end of the current line
479 eol = output.indexOf( '\n', offset );
480 if( ( eol == -1 ) || ( eol == offset ) )
481 break;
483 //kDebug( 5326 ) <<"Parsing:" << output.mid(offset, eol-offset);
485 if( !strncmp( output.data() + offset, "pub", 3 ) ||
486 !strncmp( output.data() + offset, "sec", 3 ) ||
487 !strncmp( output.data() + offset, "sub", 3 ) )
488 { // line contains key data
489 //kDebug( 5326 )<<"Key data:";
490 int pos, pos2;
492 subkey = new Subkey( "", false );
493 key->addSubkey( subkey );
495 // Key Flags
496 /* From the PGP 5 manual page for pgpk:
497 Following this column is a single character which
498 describes other attributes of the object:
500 @ The object is disabled
501 + The object is axiomatically trusted (i.e., it's
502 your key)
504 switch( output[offset+3] )
506 case ' ': // nothing special
507 break;
508 case '@': // disabled key
509 subkey->setDisabled( true );
510 key->setDisabled( true );
511 break;
512 default: // all other flags are ignored
513 //kDebug( 5326 ) <<"Unknown key flag.";
517 // Key Length
518 pos = offset + 4;
519 while( output[pos] == ' ' )
520 pos++;
521 pos2 = output.indexOf( ' ', pos );
522 subkey->setKeyLength( output.mid( pos, pos2-pos ).toUInt() );
523 //kDebug( 5326 ) <<"Key Length:"<<subkey->keyLength();
525 // Key ID
526 pos = pos2 + 1;
527 while( output[pos] == ' ' )
528 pos++;
529 pos += 2; // skip the '0x'
530 pos2 = output.indexOf( ' ', pos );
531 subkey->setKeyID( output.mid( pos, pos2-pos ) );
532 //kDebug( 5326 ) <<"Key ID:"<<subkey->keyID();
534 // Creation Date
535 pos = pos2 + 1;
536 while( output[pos] == ' ' )
537 pos++;
538 pos2 = output.indexOf( ' ', pos );
539 int year = output.mid( pos, 4 ).toInt();
540 int month = output.mid( pos+5, 2 ).toInt();
541 int day = output.mid( pos+8, 2 ).toInt();
542 QDateTime dt( QDate( year, month, day ), QTime( 00, 00 ) );
543 QDateTime epoch( QDate( 1970, 01, 01 ), QTime( 00, 00 ) );
544 // The calculated creation date isn't exactly correct because QDateTime
545 // doesn't know anything about timezones and always assumes local time
546 // although epoch is of course UTC. But as PGP 5 anyway doesn't print
547 // the time this doesn't matter too much.
548 subkey->setCreationDate( epoch.secsTo( dt ) );
550 // Expiration Date
551 // if the primary key has been revoked the expiration date is not printed
552 if( primaryKey || !key->revoked() )
554 pos = pos2 + 1;
555 while( output[pos] == ' ' )
556 pos++;
557 pos2 = output.indexOf( ' ', pos );
558 if( output[pos] == '-' )
559 { // key doesn't expire
560 subkey->setExpirationDate( -1 );
562 else if( !strncmp( output.data() + pos, "*REVOKED*", 9 ) )
563 { // key has been revoked
564 subkey->setRevoked( true );
565 key->setRevoked( true );
567 else
569 int year = output.mid( pos, 4 ).toInt();
570 int month = output.mid( pos+5, 2 ).toInt();
571 int day = output.mid( pos+8, 2 ).toInt();
572 QDateTime dt( QDate( year, month, day ), QTime( 00, 00 ) );
573 subkey->setCreationDate( epoch.secsTo( dt ) );
574 // has the key already expired?
575 if( QDateTime::currentDateTime() >= dt )
577 subkey->setExpired( true );
578 key->setExpired( true );
582 else if( key->revoked() )
583 subkey->setRevoked( true );
585 // Key algorithm (RSA, DSS, Diffie-Hellman)
586 bool sign = false;
587 bool encr = false;
588 pos = pos2 + 1;
589 while( output[pos] == ' ' )
590 pos++;
591 pos2 = output.indexOf( ' ', pos );
592 if( !strncmp( output.data() + pos, "RSA", 3 ) )
594 sign = true;
595 encr = true;
597 else if( !strncmp( output.data() + pos, "DSS", 3 ) )
598 sign = true;
599 else if( !strncmp( output.data() + pos, "Diffie-Hellman", 14 ) )
600 encr = true;
601 else
602 kDebug( 5326 )<<"Unknown key algorithm";
604 // set key capabilities of the subkey
605 subkey->setCanEncrypt( encr );
606 subkey->setCanSign( sign );
607 subkey->setCanCertify( sign );
609 if( primaryKey )
611 // Global key capabilities
612 bool canSign = false;
613 bool canEncr = false;
614 pos = pos2 + 1;
615 while( output[pos] == ' ' )
616 pos++;
617 pos2 = eol;
618 if( !strncmp( output.data() + pos, "Sign & Encrypt", 14 ) )
620 canSign = true;
621 canEncr = true;
623 else if( !strncmp( output.data() + pos, "Sign only", 9 ) )
624 canSign = true;
625 else if( !strncmp( output.data() + pos, "Encrypt only", 12 ) )
626 canEncr = true;
627 else
628 kDebug( 5326 )<<"Unknown key capability";
630 // set the global key capabilities
631 if( !key->expired() && !key->revoked() )
633 key->setCanEncrypt( canEncr );
634 key->setCanSign( canSign );
635 key->setCanCertify( canSign );
637 //kDebug( 5326 )<<"Key capabilities:"<<(key->canEncrypt()?"E":"")<<(key->canSign()?"SC":"");
638 primaryKey = false;
641 else if( !strncmp( output.data() + offset, "f16", 3 ) ||
642 !strncmp( output.data() + offset, "f20", 3 ) )
643 { // line contains a fingerprint
644 /* Examples:
645 f16 Fingerprint16 = DE 2A 77 08 78 64 7C 42 72 75 B1 A7 3E 42 3F 79
646 f20 Fingerprint20 = 226F 4B63 6DA2 7389 91D1 2A49 D58A 3EC1 5214 181E
649 int pos = output.indexOf( '=', offset+3 ) + 2;
650 QByteArray fingerprint = output.mid( pos, eol-pos );
651 // remove white space from the fingerprint
652 for ( int idx = 0 ; (idx = fingerprint.indexOf(' ', idx)) != -1 ; )
653 fingerprint.replace( idx, 1, "" );
654 assert( subkey != 0 );
655 subkey->setFingerprint( fingerprint );
656 //kDebug( 5326 )<<"Fingerprint:"<<fingerprint;
658 else if( !strncmp( output.data() + offset, "uid", 3 ) )
659 { // line contains a uid
660 int pos = offset+5;
661 QByteArray uid = output.mid( pos, eol-pos );
662 key->addUserID( uid );
663 // displaying of uids which contain non-ASCII characters is broken in
664 // PGP 5.0i; it shows these characters as \ooo and truncates the uid
665 // because it doesn't take the 3 extra characters per non-ASCII char
666 // into account. Example (with an UTF-8 encoded &ouml;):
667 // uid Ingo Kl\303\266cker <ingo.kloecker@epo
668 // because of this and because we anyway don't know which charset was
669 // used to encode the uid we don't try to decode it
671 else if ( !strncmp( output.data() + offset, "sig", 3 ) ||
672 !strncmp( output.data() + offset, "SIG", 3 ) ||
673 !strncmp( output.data() + offset, "ret", 3 ) )
674 { // line contains a signature
675 // SIG = sig with own key; ret = sig with revoked key
676 // we ignore it for now
679 offset = eol + 1;
682 return key;
686 Key*
687 Base5::parseSingleKey( const QByteArray& output, Key* key /* = 0 */ )
689 int offset;
691 // search start of header line
692 if( !strncmp( output.data(), "Type Bits", 9 ) )
693 offset = 0;
694 else
696 offset = output.indexOf( "\nType Bits" ) + 1;
697 if( offset == 0 )
698 return 0;
701 // key data begins in the next line
702 offset = output.indexOf( '\n', offset ) + 1;
703 if( offset == -1 )
704 return 0;
706 key = parseKeyData( output, offset, key );
708 //kDebug( 5326 ) <<"finished parsing keys";
710 return key;
714 KeyList
715 Base5::parseKeyList( const QByteArray& output, bool onlySecretKeys )
717 KeyList keys;
718 Key *key = 0;
719 int offset;
721 // search start of header line
722 if( !strncmp( output.data(), "Type Bits", 9 ) )
723 offset = 0;
724 else
726 offset = output.indexOf( "\nType Bits" ) + 1;
727 if( offset == 0 )
728 return keys;
731 // key data begins in the next line
732 offset = output.indexOf( '\n', offset ) + 1;
733 if( offset == -1 )
734 return keys;
738 key = parseKeyData( output, offset );
739 if( key != 0 )
741 // if only secret keys should be read test if the key is secret
742 if( !onlySecretKeys || !key->secret() )
743 keys.append( key );
744 // skip the blank line which separates the keys
745 offset++;
748 while( key != 0 );
750 //kDebug( 5326 ) <<"finished parsing keys";
752 return keys;
756 void
757 Base5::parseTrustDataForKey( Key* key, const QByteArray& str )
759 if( ( key == 0 ) || str.isEmpty() )
760 return;
762 QByteArray keyID = "0x" + key->primaryKeyID();
763 UserIDList userIDs = key->userIDs();
765 // search the start of the trust data
766 int offset = str.indexOf( "\n\n KeyID" ) + 9;
767 if( offset == -1 + 9 )
768 return;
770 offset = str.indexOf( '\n', offset ) + 1;
771 if( offset == -1 + 1 )
772 return;
774 bool ultimateTrust = false;
775 if( !strncmp( str.data() + offset+13, "ultimate", 8 ) )
776 ultimateTrust = true;
778 while( true )
779 { // loop over all trust information about this key
781 int eol;
783 // search the end of the current line
784 if( ( eol = str.indexOf( '\n', offset ) ) == -1 )
785 break;
787 if( str[offset+23] != ' ' )
788 { // line contains a validity value for a user ID
790 // determine the validity
791 Validity validity = KPGP_VALIDITY_UNKNOWN;
792 if( !strncmp( str.data() + offset+23, "complete", 8 ) )
793 if( ultimateTrust )
794 validity = KPGP_VALIDITY_ULTIMATE;
795 else
796 validity = KPGP_VALIDITY_FULL;
797 else if( !strncmp( str.data() + offset+23, "marginal", 8 ) )
798 validity = KPGP_VALIDITY_MARGINAL;
799 else if( !strncmp( str.data() + offset+23, "invalid", 7 ) )
800 validity = KPGP_VALIDITY_UNDEFINED;
802 // determine the user ID
803 int pos = offset + 33;
804 QString uid = str.mid( pos, eol-pos );
806 // set the validity of the corresponding user ID
807 for( UserIDList::Iterator it = userIDs.begin(); it != userIDs.end(); ++it )
808 if( (*it)->text() == uid )
810 kDebug( 5326 )<<"Setting the validity of"<<uid<<" to"<<validity;
811 (*it)->setValidity( validity );
812 break;
816 offset = eol + 1;
821 } // namespace Kpgp